This is the part 9 of the series: .NET Microservice with ABP
Posts in the Series
Part 3. Administration Service
Part 8. Identity server and Angular App
Part 9. Distributed event bus (this post)
Table of contents
Intro
In this post, we will see how to set up service-to-service communication. We will use RabbitMQ as an event bus. Each service will have a RabbitMQ configuration. ABP comes with the inbuilt support to use the RabbitMQ for events. The packages are already available in the shared project we created in part 2. We just have to add the configuration in the appsettings and implement the events.
Update appsettings.json
Administration service
1"RabbitMQ": { 2 "Connections": { 3 "Default": { 4 "HostName": "localhost" 5 } 6 }, 7 "EventBus": { 8 "ClientName": "Tasky_Administration", 9 "ExchangeName": "Tasky" 10 } 11}
Identity service
1"RabbitMQ": { 2 "Connections": { 3 "Default": { 4 "HostName": "localhost" 5 } 6 }, 7 "EventBus": { 8 "ClientName": "Tasky_Identity", 9 "ExchangeName": "Tasky" 10 } 11}
SaaS service
1"RabbitMQ": { 2 "Connections": { 3 "Default": { 4 "HostName": "localhost" 5 } 6 }, 7 "EventBus": { 8 "ClientName": "Tasky_SaaS", 9 "ExchangeName": "Tasky" 10 } 11}
Identity Server
1"RabbitMQ": { 2 "Connections": { 3 "Default": { 4 "HostName": "localhost" 5 } 6 }, 7 "EventBus": { 8 "ClientName": "Tasky_AuthServer", 9 "ExchangeName": "Tasky" 10 } 11}
Tenant Created Event handlers for Administration Service
When the tenant is created in the SaaS service, the administration service needs to know about the newly created tenant so that we can add permission for the new tenant. This permission is given to the admin role. So that when the user is created with the admin role then he can log in to the newly created tenant. The tenant management module triggers a tenant-created event every time a tenant is created. We don't have to do anything extra in our code. We just have to handle the created event. Following is how you can manage the tenant-created event.
1using System; 2using System.Linq; 3using System.Threading.Tasks; 4using Microsoft.Extensions.Logging; 5using Volo.Abp.Authorization.Permissions; 6using Volo.Abp.DependencyInjection; 7using Volo.Abp.EventBus.Distributed; 8using Volo.Abp.MultiTenancy; 9using Volo.Abp.PermissionManagement; 10using Volo.Abp.Uow; 11 12namespace Tasky.Administration.EventHandler; 13 14public class TenantCreatedEventHandler : IDistributedEventHandler<TenantCreatedEto>, ITransientDependency 15{ 16 private readonly ICurrentTenant _currentTenant; 17 private readonly ILogger<TenantCreatedEventHandler> _logger; 18 private readonly IPermissionDataSeeder _permissionDataSeeder; 19 private readonly IPermissionDefinitionManager _permissionDefinitionManager; 20 private readonly IUnitOfWorkManager _unitOfWorkManager; 21 22 public TenantCreatedEventHandler( 23 ICurrentTenant currentTenant, 24 IUnitOfWorkManager unitOfWorkManager, 25 IPermissionDefinitionManager permissionDefinitionManager, 26 IPermissionDataSeeder permissionDataSeeder, 27 ILogger<TenantCreatedEventHandler> logger) 28 { 29 _currentTenant = currentTenant; 30 _unitOfWorkManager = unitOfWorkManager; 31 _permissionDefinitionManager = permissionDefinitionManager; 32 _permissionDataSeeder = permissionDataSeeder; 33 _logger = logger; 34 } 35 36 public async Task HandleEventAsync(TenantCreatedEto eventData) 37 { 38 try 39 { 40 await SeedDataAsync(eventData.Id); 41 } 42 catch (Exception ex) 43 { 44 await HandleErrorTenantCreatedAsync(eventData, ex); 45 } 46 } 47 48 private Task HandleErrorTenantCreatedAsync(TenantCreatedEto eventData, Exception ex) 49 { 50 throw new NotImplementedException(); 51 } 52 53 private async Task SeedDataAsync(Guid? tenantId) 54 { 55 _logger.LogInformation($"Seeding ${tenantId}"); 56 using (_currentTenant.Change(tenantId)) 57 { 58 var abpUnitOfWorkOptions = new AbpUnitOfWorkOptions {IsTransactional = true}; 59 using var uow = _unitOfWorkManager.Begin(abpUnitOfWorkOptions, true); 60 var multiTenancySide = tenantId is null 61 ? MultiTenancySides.Host 62 : MultiTenancySides.Tenant; 63 64 var permissionNames = _permissionDefinitionManager 65 .GetPermissions() 66 .Where(p => p.MultiTenancySide.HasFlag(multiTenancySide)) 67 .Where(p => !p.Providers.Any() || p.Providers.Contains(RolePermissionValueProvider.ProviderName)) 68 .Select(p => p.Name) 69 .ToArray(); 70 71 await _permissionDataSeeder.SeedAsync( 72 RolePermissionValueProvider.ProviderName, 73 "admin", 74 permissionNames, 75 tenantId 76 ); 77 78 await uow.CompleteAsync(); 79 } 80 } 81}
Tenant Created Event handlers for Identity Service
Similar to what we have done in the administration service. we need to handle the tenant created event in the identity service to create the admin user for the newly created tenant. we can create the event handler and seed the user with the default username and password.
1using System; 2using System.Threading.Tasks; 3using Microsoft.Extensions.Logging; 4using Volo.Abp.DependencyInjection; 5using Volo.Abp.EventBus.Distributed; 6using Volo.Abp.MultiTenancy; 7using Volo.Abp.Identity; 8using System.Collections.Generic; 9 10namespace Tasky.IdentityService.EventHandler; 11 12public class TenantCreatedEventHandler : IDistributedEventHandler<TenantCreatedEto>, ITransientDependency 13{ 14 private readonly ICurrentTenant _currentTenant; 15 private readonly ILogger<TenantCreatedEventHandler> _logger; 16 private readonly IIdentityDataSeeder _identityDataSeeder; 17 public TenantCreatedEventHandler( 18 ICurrentTenant currentTenant, 19 IIdentityDataSeeder identityDataSeeder, 20 ILogger<TenantCreatedEventHandler> logger) 21 { 22 _currentTenant = currentTenant; 23 _identityDataSeeder = identityDataSeeder; 24 _logger = logger; 25 } 26 27 public async Task HandleEventAsync(TenantCreatedEto eventData) 28 { 29 try 30 { 31 using (_currentTenant.Change(eventData.Id)) 32 { 33 34 _logger.LogInformation($"Creating admin user for tenant {eventData.Id}..."); 35 await _identityDataSeeder.SeedAsync( 36 eventData.Properties.GetOrDefault(IdentityDataSeedContributor.AdminEmailPropertyName) ?? "admin@abp.io", 37 eventData.Properties.GetOrDefault(IdentityDataSeedContributor.AdminPasswordPropertyName) ?? "1q2w3E*", 38 eventData.Id 39 ); 40 } 41 } 42 catch (Exception ex) 43 { 44 await HandleErrorTenantCreatedAsync(eventData, ex); 45 } 46 } 47 48 private Task HandleErrorTenantCreatedAsync(TenantCreatedEto eventData, Exception ex) 49 { 50 throw new NotImplementedException(); 51 } 52}
Run
Use tye to run all the services. When the service starts it will create the queue in the RabbitMQ. So make sure you have RabbitMQ running.
1tye run
Test
To test the event bus. Login to the angular app in http://localhost:4200
as admin and create a new tenant. Once the tenant is created logout and try to login to the tenant you just created. if the event bus is working then you can login with default username and password for the newly created tenant.