With the aid of this tutorial, you should have a Nexus Adapter up and running in a couple of minutes.
Prerequisites
- A development environment. This is our recommendation:
- Windows 10
- Visual Studio at least 2019 Professional
- If you install it, make sure that you select Workloads:
Azure development
- If you install it, make sure that you select Workloads:
- .NET Core SDK (at least 2.2)
- Some things from the Nexus integration team of your organization
- A git repository with a ready-made template
- Read access to the organizations NuGet packages
Run for the first time
- Open the solution file of the template
- Start the application
- You should see a welcome message and a link to the Open API.
Example
This tutorial is based on the Nexus Code Examples repositry and the NetCoreAdapter
folder.
If you want to use that then go through the repository and rename all references from Crm
to the name of the system you are building an adapter for.
Authenticate to the Business API
As long as you have RunTimeLevel
set to Development
, the services does not require authentication and your calls to the Business API will only be logged, never actually called. If you want to test for real, then you have to change the level to Test
and autenticate your app with the Business API. This means that you will need to enter the endpoint, your client id and your client secret. The client id and client secrets you will get from your ICC.
You should never store secrets with your code. By following the steps below, you will be using a local file on your PC for your secrets, see Secret Manager tool.
- Right click on your
Service
project, selectManage User Secrets
- Add your BusinessApi autentication:
{
"BusinessApi": {
"Endpoint": "https://tst-business-api-acme-nexus.azurewebsites.net",
"ClientId": "my-adapter",
"ClientSecret": "xxxxxxxxxxxx"
}
}
Implement the capablities
The adapters now needs to implement the capabilities they are responsible for.
In the example repo we are going to look at the Membercapablity and how to implement that.
Make sure that your organization have defined the controllers and contracts for the capability in the Business API.
See the Nexus Business API tutorial for how to do that.
Nuget Packages
Install your organizations Nuget packages. They'll deliver the Contracts and Controllers the adapter should implement.
Create the capability
You only need to point the Services from the Business API to where your logic in the adapter is.
This class in the Capabilities/Onboarding/Logic/OnBoardingCapability.cs
is pointing from the services to the logic needed for the current capability to be implemented.
/// <inheritdoc />
public class OnBoardingCapability : IOnBoardingCapability
{
/// <inheritdoc />
public OnBoardingCapability(ICrmSystem system)
{
ApplicantService = new ApplicantLogic(system);
MemberService = new MemberLogic(system);
}
/// <inheritdoc />
public IApplicantService ApplicantService { get; }
/// <inheritdoc />
public IMemberService MemberService { get; }
}
This class inherits from the IOnBoardingCapability which exists in the Nuget Library from the Business Api. That interface also contains the ApplicantService and MemberService which we will implement now in with the MemberLogic
.
Implement the logic
Now it's time to implement the logic of your capability. The Contracts and Interfaces are implemented in the Nuget package.
In the file Capabilities/Onboarding/Logic/MemberLogic.cs
the logic file looks like this:
/// <summary>
/// Implements logic for of <see cref="IMemberService"/>
/// </summary>
public class MemberLogic : IMemberService
{
private readonly ICrmSystem _crmSystem;
/// <summary>
/// Constructor
/// </summary>
public MemberLogic(ICrmSystem crmSystem)
{
_crmSystem = crmSystem;
}
/// <inheritdoc />
public async Task<IEnumerable<Member>> ReadAllAsync(int limit = 2147483647, CancellationToken token = new CancellationToken())
{
var contacts = await _crmSystem.ContactFunctionality.ReadAllAsync();
var members = contacts.Select(contact => new Member().From(contact));
return await Task.FromResult(members);
}
/// <inheritdoc />
public async Task DeleteAsync(string id, CancellationToken token = new CancellationToken())
{
InternalContract.RequireNotNullOrWhiteSpace(id, nameof(id));
await _crmSystem.ContactFunctionality.Delete(id.ToGuid());
}
/// <inheritdoc />
public async Task DeleteAllAsync(CancellationToken token = new CancellationToken())
{
InternalContract.Require(!FulcrumApplication.IsInProductionOrProductionSimulation, "This method can\'t be called in production.");
await _crmSystem.ContactFunctionality.DeleteAll();
}
}
Just inherit the interface IMemberService and point all the implemented methods to the repositories from your system. All the methods you need to implement is now given to you in this file and all you need to do is concentrate on calling your system, in this case via ICrmSystem
.
Register the capability
Now you can register the capability in startup with the line
services.AddScoped<IOnBoardingCapability, OnBoardingCapability>();
where the IOnBoardingCapability
is the interface from the Nuget with all the methods you should implement in the adapter and the OnBoardingCapability
is the class that you have built.
Now the capabilities have been implemeted with the functions defined in the Business API.
This way the controllers you should implement in your adapter are also created and exposed in the OpenApi.
Connect to the system
Now it's time to add the connection to the system you are building your adapter to.
Do this in the project called Crm.System
In the folder Contracts add the interface for IContactFunctionality
. This will be the methods you implement for you system that the logic will consume.
The models in Contracts\
are the models in the Api you are connecting to.
Like the customer in Contracts\Model\Contact.cs
/// <summary>
/// Information about a customer
/// </summary>
public class Contact
{
/// <summary>
/// The internal id of the customer.
/// </summary>
public Guid Id { get; set; }
/// <summary>
/// The lead that led to this contact.
/// </summary>
public Guid? OriginatingLeadId { get; set; }
/// <summary>
/// The unique customer number to be used in all communication
/// </summary>
public string CustomerNumber { get; set; }
/// <summary>
/// The name of the customer
/// </summary>
public string Name { get; set; }
}
With this model you define the connections to your endpoints, in the example we'll look at the file SDK\ContactFunctionality.cs
.
public class ContactFunctionality : IContactFunctionality
{
private static readonly List<Contact> Items = new List<Contact>();
private int _memberCount;
/// <inheritdoc />
public Task<IEnumerable<Contact>> ReadAllAsync()
{
return Task.FromResult((IEnumerable<Contact>) Items);
}
internal Task<Guid> CreateAsync(Contact item)
{
item.Id = Guid.NewGuid();
_memberCount++;
item.CustomerNumber = $"Member {_memberCount}";
Items.Add(item);
return Task.FromResult(item.Id);
}
/// <inheritdoc />
public Task Delete(Guid id)
{
var item = Items.FirstOrDefault(i => i.Id == id);
if (item != null) Items.Remove(item);
return Task.CompletedTask;
}
/// <inheritdoc />
public Task DeleteAll()
{
Items.Clear();
return Task.CompletedTask;
}
}
The ReadAll method here is the one we used above in the file MemberLogic.cs
with the following line
var contacts = await _crmSystem.ContactFunctionality.ReadAllAsync();
Now the capabilities your system is responsible for are implemented.
Nexus Link Libraries
When connecting to your system Nexus Link Libraries offers a lot of help in the Nexus.Link.Libraries.Web
see Nexus.Link.Libraries for code and documentation. For example there are helpers for the Rest Client implementations.
Events
It may also be that your system is responsible for publishing events. It may be business events or data synchronization events or both. The contracts for the events are, just like for the other capability contracts, defined in the Business API and they themselves contains the tools for publishing.
Definition
This type of defintion is done in the Business API and NOT in the adapter. It is only there in the example repository to make it self-containing.
In the example repository we have the event MemberApprovedEvent
which looks like this.
/// <summary>
/// This event is published whenever an applicant is approved to become a member.
/// </summary>
public class MemberApprovedEvent : IPublishableEvent
{
/// <inheritdoc />
[JsonIgnore]
public EventMetadata Metadata { get; } = new EventMetadata("Member", "Approved", 1, 1);
/// <summary>
/// The
/// </summary>
public string MemberId { get; set; }
/// <summary>
/// The time when the member was approved
/// </summary>
public string ApprovedAt { get; set; }
}
It inherits from the IPublishable events which contains the information needed to publish it. This is the event that we will publish.
Triggering the event from your system
In the example we will publish the event MemberApprovedEvent
when an applicant is approved in your system. You are responsible to post to the adapter every time the event has happened. In the example you have to call POST /api/Applicants/{id}/Approve
every time this happens.
In the ApplicantLogic.cs
file located at Capabilities\Onboarding\Logic
when an applicant is approved it will go to the SDK
and the implementation of connection to the system.
In the code for qualifiying an applicant in Crm.System\Sdk\LeadFunctionality.cs
/// <exception cref="BusinessRuleException"></exception>
/// <inheritdoc />
public async Task<Guid> QualifyAsync(Guid id)
{
var lead = await ReadAsync(id);
if (lead == null) throw new NotFoundException("Lead not found");
switch (lead.Status)
{
case Lead.StatusEnum.Active:
lead.Status = Lead.StatusEnum.Qualified;
lead.Reason = "Qualified";
lead.UpdatedAt = DateTimeOffset.Now;
var contact = new Contact
{
Name = lead.Name,
OriginatingLeadId = lead.Id
};
var contactId = await _contactFunctionality.CreateAsync(contact);
await _adapterService.LeadWasQualified(lead.Id, contactId, lead.UpdatedAt);
return contactId;
case Lead.StatusEnum.Qualified:
throw new BusinessRuleException(1, "The lead has already been qualified.");
case Lead.StatusEnum.Rejected:
throw new BusinessRuleException(2, "The lead has already been rejected.");
default:
throw new ProgrammersErrorException($"Unknown lead status: {lead.Status}");
}
}
the LeadWasQualified
method is called.
Note that the LeadWasQualified is a part of the adapter-implenentation. Due to this this method is located in the Service again at Crm.NexusAdapter.Servic\Capabilities\Adapter\Logic\AdapterServiceForSystem.cs
This chain of code is due that that in your implementation of what will happen when an applicant is approved you will also create the contact and set a new status etc. In that code it is preferably to also publish the event when all the other work is done.
/// <inheritdoc />
public class AdapterServiceForSystem : IAdapterService
{
private readonly IIntegrationCapability _integrationCapability;
/// <inheritdoc />
public AdapterServiceForSystem(IIntegrationCapability integrationCapability)
{
_integrationCapability = integrationCapability;
}
/// <inheritdoc />
public async Task LeadWasQualified(Guid leadId, Guid contactId, DateTimeOffset approvedAt)
{
var @event = new MemberApprovedEvent
{
MemberId = contactId.ToIdString(),
ApprovedAt = approvedAt.ToIso8061Time()
};
await _integrationCapability.BusinessEventService.PublishAsync(@event);
}
}
When the LeadWasQualified
method is called the parameters are used to create the event MemberApproved
since it is now approved and created. The event-type and contract comes from the Nuget package created by your organization in the Business API.
Publishing event
Now you are ready to publish the event defined by your organization. This is easily done by calling PublishAsync
with your event.
The code is this small snippet from the file above.
var @event = new MemberApprovedEvent
{
MemberId = contactId.ToIdString(),
ApprovedAt = approvedAt.ToIso8061Time()
};
await _integrationCapability.BusinessEventService.PublishAsync(@event);
This is all that is needed to actually publish the event.
If you get a successful response when publishing the event you have done your part and the rest is taken care of by Nexus Link.
If your startup is inherting from NexusAdapterStartup
then the events are automatically connected to your Business API. Otherwise you can look in the example code repository how the event publisher is using the IIntegrationCapability
which contains PublishAsync
.
It is not enough for the adapter to only publish an event with this contract when it is created. The adapter also need to be registered as a publisher in Business Events Service. This is done by your ICC.