Anto Subash.

Published on

Distributed Event Bus - .NET Microservice with ABP - Part 9

This is the part 9 of the series: .NET Microservice with ABP

Posts in the Series

Part 1. Initial Setup

Part 2. Shared Project

Part 3. Administration Service

Part 4. Identity Service

Part 5. SaaS Service

Part 6. DB Migration

Part 7. Yarp and Tye

Part 8. Identity server and Angular App

Part 9. Distributed event bus (this post)

Part 10. Docker and CI/CD

Part 11. Add a New service

Part 12. Central Logging

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.