Microservice Context Mapping¶
Direct calls via network (Synchronous communication)¶
The Client Credentials flow is a server to server flow and there is no user authentication involved in the process. Instead of the user, the client ID (service itself) as the subject is authenticated with a predefined secret. The resulting access token contains client information instead of user information.
- Do add the project reference of
Gridlab.GMSS.DataManagement.Application.ContractstoGridlab.GMSS.Odms.Application.Contractsand then add the module dependency attribute to theOdmsApplicationContractsModuleclass.
[DependsOn(
typeof(DataManagementApplicationContractsModule),
// ... other module layers will be here
)]
public class OdmsApplicationContractsModule : AbpModule
{
}
Now you can call File Descriptor Application Services from the Model Application Service:
[Authorize(OdmsPermissions.ModelManagement.Permission)]
[RequiresFeature(OdmsFeatures.ModelManagement)]
public class ModelAppService : OdmsAppService, IModelAppService
{
protected IModelManager ModelManager { get; }
protected IModelRepository ModelRepository { get; }
/* Can use application services of others only if;
* They are parts of another module / microservice.
* The current module has only reference to the application contracts of the used module.
*/
protected IFileDescriptorAppService FileDescriptorAppService { get; } //
public ModelAppService(
IModelManager modelManager,
IModelRepository modelRepository,
IFileDescriptorAppService fileDescriptorAppService)
{
ModelManager = modelManager;
ModelRepository = modelRepository;
FileDescriptorAppService = fileDescriptorAppService;
}
Since there is no implementation of IFileDescriptorAppService in Gridlab.GMSS.Odms.Application, you need to configure it to make remote HTTP calls.
- Do add the project reference of
Gridlab.GMSS.DataManagement.HttpApi.Clientproject toGridlab.GMSS.Odms.Applicationand then add the module dependency toOdmsApplicationModule.
[DependsOn(
typeof(DataManagementHttpApiClientModule),
// ... other module layers will be here
)]
public class OdmsApplicationModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<AbpHttpClientBuilderOptions>(options =>
{
options.ProxyClientBuildActions.Add((remoteServiceName, clientBuilder) =>
{
clientBuilder.AddTransientHttpErrorPolicy(policyBuilder =>
policyBuilder.WaitAndRetryAsync(
3,
i => TimeSpan.FromSeconds(Math.Pow(2, i))
)
);
});
});
}
If you want to add retry logic for the failing remote HTTP calls for the client proxies, you can configure the
AbpHttpClientBuilderOptionsin the PreConfigureServices method of your module class.
This example uses the Microsoft.Extensions.Http.Polly package. You also need to import the Polly namespace (using Polly;) to be able to use the WaitAndRetryAsync method.
Open the appsettings.json file in the Gridlab.GMSS.Odms.HttpApi.Host project and add RemoteServices section to route HTTP requests to the where Gridlab.GMSS.DataManagement.Application is hosted. In this case Gridlab.GMSS.Odms.AuthServer is hosting.

appsettings.json of Gridlab.GMSS.Odms.HttpApi.Host project should look like below:
"RemoteServices": {
"FileManagement": {
"BaseUrl": "https://localhost:44397", // Remote adress of Gridlab.GMSS.Odms.AuthServer
"UseCurrentAccessToken": "false"
}
}
- Do sort module layers before common layers in
singleinstance version of GMSS.
Example HttpApi:
[DependsOn(
// ... other module layers will be here
// Module Packages
typeof(OdmsHttpApiModule),
// Base Packages
typeof(DataManagementHttpApiModule),
//
typeof(GMSSApplicationContractsModule)
)]
public class GMSSHttpApiModule : AbpModule
{
}
Example Web Hosting:
[DependsOn(
// ... other module layers will be here
typeof(OdmsWebModule), // main module
typeof(DataManagementWebModule), // base module
//
typeof(GMSSHttpApiClientModule),
typeof(GMSSHttpApiModule)
)]
public class GMSSWebModule : AbpModule
{
}
Configuring Auto-Discovery Endpoint¶
-
Do to automate requesting access token and adding it as bearer to the request headers; add
Volo.Abp.Http.Client.IdentityModelNuGet package to theGridlab.GMSS.Odms.HttpApi.Hostproject. -
Do open
Gridlab.GMSS.Odms.HttpApi.Hostand add the following line (update the version attribute according to your project!)
<PackageReference Include="Volo.Abp.Http.Client.IdentityModel" Version="7.3.1" />
- Do add DependsOn attribute, open
OdmsHttpApiHostModule.csclass and add the following module dependency
[DependsOn(
typeof(AbpHttpClientIdentityModelModule),
// ... other module layers will be here
)]
public class OdmsHttpApiHostModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
}
}
- Do add
IdentityClientssection toappsettings.jsonfile of theGridlab.GMSS.Odms.HttpApi.Hostto configureClient Credentialaccess token request with client secret to theAuthServerend point.
"IdentityClients": {
"Default": {
"GrantType": "client_credentials",
"ClientId": "DataManagement_App",
"ClientSecret": "1q2w3e*",
"Authority": "https://localhost:44397",
"Scope": "Odms"
}
}
OpenIdDict Configuration¶
Create New Client:
DataManagement_App itself is a new client and you need to add the Odms as a client under CreateApplicationsAsync as well. Open IdentityServerDataSeeder.cs and add the below line in
- Do open
OdmsAuthServerDataSeeder.csand add the below lines atGridlab.GMSS.Odms.AuthServerproject.
// Service Client
var serviceClientId = configurationSection["DataManagement_App:ClientId"];
if (!serviceClientId.IsNullOrWhiteSpace())
{
await CreateApplicationAsync(
name: serviceClientId,
type: OpenIddictConstants.ClientTypes.Confidential,
consentType: OpenIddictConstants.ConsentTypes.Implicit,
displayName: "DataManagement Service",
secret: configurationSection["DataManagement_App:ClientSecret"] ?? "1q2w3e*",
grantTypes: new List<string>
{
OpenIddictConstants.GrantTypes.ClientCredentials,
},
scopes: commonScopes,
permissions: FileManagementPermissions.GetAll().ToList() // Since Odms will use every possible method of the FileDescriptorAppService, we are adding all possible permission for this client.
);
}
Gridlab.GMSS.Odms.HttpApi.HostappsettingsIdentityClientssection data must match with the client creation data.
FileDescriptorAppService has permission authorization and will return 401 unauthorized exception when a request has been made. To overcome this issue, add the required permissions of the service you are making to.
public static class FileManagementPermissions
{
public const string GroupName = "FileManagement";
public static class DirectoryDescriptor
{
public const string Default = GroupName + ".DirectoryDescriptor";
public const string Create = Default + ".Create";
public const string Update = Default + ".Update";
public const string Delete = Default + ".Delete";
}
public static class FileDescriptor
{
public const string Default = GroupName + ".FileDescriptor";
public const string Create = Default + ".Create";
public const string Update = Default + ".Update";
public const string Delete = Default + ".Delete";
}
public static string[] GetAll()
{
return ReflectionHelper.GetPublicConstantsRecursively(typeof(FileManagementPermissions));
}
}
Events via message queues (Asynchronous communication)¶
-
Do publishing bounded context publishes an event, subscribing bounded context subscribes to it, this approach doesn't suffer from the temporal issues assoicated with REST communication.
-
Do install
RabbitMQmessage broker. See Installing RabbitMQ -
Do install
Volo.Abp.EventBus.RabbitMQmodule bothGridlab.GMSS.ModuleName.AuthServerandGridlab.GMSS.ModuleName.HttpApiHosthosting projects
use the DependsOn attribute to include the installed package services.
Example Publishing bounded context:
[DependsOn(
// ... other module layers will be here
typeof(AbpEventBusRabbitMqModule)
)]
public class ModuleNameAuthServerModule : AbpModule
Example Subscribing bounded context:
[DependsOn(
// ... other module layers will be here
typeof(AbpEventBusRabbitMqModule)
)]
public class ModuleNameHttpApiHostModule : AbpModule
configure rabbitmq parameters:
Configure<AbpRabbitMqOptions>(options =>
{
options.Connections.Default.Ssl.Enabled = bool.Parse(configuration["RabbitMQ:IsSslEnabled"] ?? "false");
options.Connections.Default.UserName = configuration["RabbitMQ:Connections:Default:UserName"] ?? "user";
options.Connections.Default.Password = configuration["RabbitMQ:Connections:Default:Password"] ?? "bitnami";
options.Connections.Default.HostName = configuration["RabbitMQ:Connections:Default:HostName"] ?? "localhost";
options.Connections.Default.Port = int.Parse(configuration["RabbitMQ:Connections:Default:Port"] ?? (bool.Parse(configuration["RabbitMQ:IsSslEnabled"]) ? "5671" : "5672"));
if (bool.Parse(configuration["RabbitMQ:IsSslEnabled"]))
options.Connections.Default.Uri = new Uri(configuration["RabbitMQ:Connections:Default:Uri"]); // Uri can't be null
});
Configure<AbpRabbitMqEventBusOptions>(options =>
{
options.ClientName = configuration["RabbitMQ:EventBus:ClientName"];
options.ExchangeType = configuration["RabbitMQ:EventBus:ExchangeType"];
options.ExchangeName = configuration["RabbitMQ:EventBus:ExchangeName"];
});
Example Publishing bounded context:
"RabbitMQ": {
"IsSslEnabled": "false",
"Connections": {
"Default": {
"HostName": "127.0.0.1",
"Port": "5672",
"UserName": "user",
"Password": "bitnami",
"Uri": "amqps://127.0.0.1:5671"
}
},
"EventBus": {
"ClientName": "AuthServer", // <== Client Name is the name of this application
"ExchangeType": "topic", // Options: direct, topic, fanout, headers
"ExchangeName": "GMSS"
}
}
Example Subscribing bounded context:
"RabbitMQ": {
"IsSslEnabled": "false",
"Connections": {
"Default": {
"HostName": "127.0.0.1",
"Port": "5672",
"UserName": "user",
"Password": "bitnami",
"Uri": "amqps://127.0.0.1:5671"
}
},
"EventBus": {
"ClientName": "Odms", // <== Client Name is the name of this application
"ExchangeType": "topic", // Options: direct, topic, fanout, headers
"ExchangeName": "GMSS"
}
}
- Do define
IDistributedEventHandlerto hande domain or entity events
Example UserSynchronizer:
public class OdmsUserSynchronizer :
IDistributedEventHandler<EntityUpdatedEto<UserEto>>,
ITransientDependency
{
protected IOdmsUserRepository UserRepository;
protected IOdmsUserLookupService UserLookupService;
private readonly ILogger<OdmsUserSynchronizer> _logger;
public OdmsUserSynchronizer(
IOdmsUserRepository userRepository,
IOdmsUserLookupService userLookupService,
ILogger<OdmsUserSynchronizer> logger)
{
UserRepository = userRepository;
UserLookupService = userLookupService;
_logger = logger;
}
public virtual async Task HandleEventAsync(EntityUpdatedEto<UserEto> eventData)
{
if (eventData.Entity.TenantId.HasValue)
{
var user = await UserRepository.FindAsync(eventData.Entity.Id);
if (user == null)
{
user = await UserLookupService.FindByIdAsync(eventData.Entity.Id);
if (user == null)
{
return;
}
}
_logger.LogInformation($"Syncing {eventData.Entity.UserName} user information.");
if (user.Update(eventData.Entity))
{
await UserRepository.UpdateAsync(user);
}
}
}
}
- Do define
IDistributedEventHandlerto hande persona creation atGridlab.GMSS.ModuleName.HttpApiHostproject.
public class PersonaCreationHandler :
IDistributedEventHandler<EntityCreatedEto<UserEto>>,
ITransientDependency
{
protected IOdmsUserRepository UserRepository;
protected IOdmsUserLookupService UserLookupService;
private readonly ILogger<OdmsUserSynchronizer> _logger;
public PersonaCreationHandler(
IOdmsUserRepository userRepository,
IOdmsUserLookupService userLookupService,
ILogger<OdmsUserSynchronizer> logger)
{
UserRepository = userRepository;
UserLookupService = userLookupService;
_logger = logger;
}
/* If you perform database operations and use the repositories inside the event handler
* You may need to create a unit of work, because some repository methods need to work inside an active unit of work.
* Make the handle method virtual and add a [UnitOfWork] attribute for the method
*/
[UnitOfWork]
public virtual async Task HandleEventAsync(EntityCreatedEto<UserEto> eventData)
{
if (eventData.Entity.TenantId.HasValue)
{
var user = await UserRepository.FindAsync(eventData.Entity.Id);
if (user == null)
{
_logger.LogInformation($"Creating {eventData.Entity.UserName} user information.");
await UserRepository.InsertAsync(
entity: new OdmsUser(eventData.Entity),
autoSave: false
);
}
}
}
}