Skip to content

Entity Management

The purpose of this service is to identify entities uniquely across all the ecosystem. The Entity Management service also stores the name and description of all the Suite entities and even its properties.

Entity Management Descriptor Client

The Entity Management Descriptor Client provides an API to describe entities and its properties. In order to use the EntityDescriptorClient we need to add the project reference:

XML
<ProjectReference Include="$(ServicesPath)EntityManagement/ITsynch.Suite.EntityManagement.EntityDescriptorClient/ITsynch.Suite.EntityManagement.EntityDescriptorClient.csproj" />

Then we add the dependency in our Suite module:

C#
1
2
3
4
5
builder.DependsOn<EntityDescriptorClientModule, EntityDescriptorClientOptions>(options =>
    {
        // We talk about this later.
        options.SetLocalizationResource<EquipmentLocalizationResources>();
    });

Describing entities

First, we need to create a localization resource and a localization key and value for all entities and properties. This may live in the Application project. We create the key constants in the localization resources file:

C#
1
2
3
4
5
6
7
8
public class EquipmentLocalizationResources
{
    public static readonly string EquipmentName = "EQUIPMENT_NAME";
    public static readonly string EquipmentDescription = "EQUIPMENT_DESCRIPTION";

    public static readonly string EquipmentCorrelationIdName = "EQUIPMENT_CORRELATION_ID_NAME";
    public static readonly string EquipmentCorrelationIdDescription = "EQUIPMENT_CORRELATION_ID_DESCRIPTION";
}

And we add the corresponding keys' translations to the translations file (e.g.: en.json ).

XML
1
2
3
4
5
6
7
8
9
{
    "culture": "en",
    "texts": {        
        "EQUIPMENT_NAME": "Equipment",
        "EQUIPMENT_DESCRIPTION": "An Equipment is an ...",
        "EQUIPMENT_CORRELATION_ID_NAME": "Correlation Id",
        "EQUIPMENT_CORRELATION_ID_DESCRIPTION": "ID that correlates an specific Equipment",
    }
}

Then, for each domain entity, we need to create a class that implements IEntityDescriptor<TEntity>. In the Describe method, we configure the entity and its properties. This is an example for the Equipments bounded context and Equipment entity.

C#
public class EquipmentDescriptor : IEntityDescriptor<Equipment>
{
    public void Describe(EntityDescriptorBuilder<Equipment> builder)
    {
        builder.DescribeEntity(
            new Guid("ae000000-ac11-0242-3h91-08dab2055a68"),
            "Equipments_Equipment",
            description =>
            {
                description.Name = EquipmentLocalizationResources.EquipmentName;
                description.Description = EquipmentLocalizationResources.EquipmentDescription;
            });

        builder.DescribeProperty(
            x => x.CorrelationId,
            new Guid("ae000000-ac11-0242-0e69-08dab2055a68"),
            description =>
            {
                description.Name = EquipmentLocalizationResources.EquipmentCorrelationIdName;
                description.Description = EquipmentLocalizationResources.EquipmentCorrelationIdDescription;
            });
    }
}

Important

Make sure all the GUIDs are different and generated by NewId library, you can use this fiddle to generate them easily. All entity well known names will be in the following structure: BoundedContext_EntityName.

Set localization resource

As described before, you can set a localization resource when depending on EntityDescriptorClientModule. That will be used to translate names and descriptions.

Also, we consider the possibility of having a different localization resource for an entity descriptor. This is a recurring situation when talking about client modules. That entity will be described by each service that depends on it. In that case, you can use the same Localization resource, avoiding duplicate translations.

C#
public class EquipmentViewDescriptor : IEntityDescriptor<EquipmentView>
{
    public void Describe(EntityDescriptorBuilder<EquipmentView> builder)
    {
        builder.SetLocalizationResource<AnotherLocalizationResource>();
        builder.DescribeEntity(
            new Guid("ae000000-ac11-0242-3h91-08dab2055a68"),
            "Equipments_EquipmentView",
            description =>
            {
                description.Name = EquipmentLocalizationResources.EquipmentName;
                description.Description = EquipmentLocalizationResources.EquipmentDescription;
            });

        builder.DescribeProperty(
            x => x.CorrelationId,
            new Guid("ae000000-ac11-0242-0e69-08dab2055a68"),
            description =>
            {
                description.Name = EquipmentLocalizationResources.EquipmentCorrelationIdName;
                description.Description = EquipmentLocalizationResources.EquipmentCorrelationIdDescription;
            });
    }
}

Validation Test

In order to make sure we described things correctly, we should add a validation test to our tests battery in order to ensure that all the properties are described.

C#
using ITsynch.Suite.EntityManagement.EntityDescriptorClient;
using ITsynch.Suite.Equipments.Application;
using ITsynch.Suite.Testing.IntegrationTests;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
using Xunit.Abstractions;

namespace ITsynch.Suite.Equipments.Tests;

public class EntityDescriptorsTests: BaseHostBuildingIntegrationTest<EquipmentsApplicationModule>
{
    public EntityDescriptorsTests(ITestOutputHelper helper) : base(helper)
    {
    }

    [Fact]
    public void PropertiesAreFullyDescribed()
    {
        // Ensure all the properties are described.
        this.ServiceProvider!.GetRequiredService<IDescriptorValidator>().Validate();
    }
}

If this test passes, you are done! The descriptions will be seeded when the application starts. This means that when you add/edit any description and restart the app, the descriptions will be added/updated.

Adopt Entity Management for referencing entities

If your domain entity needs a reference to a domain entity (e.g.: Spare), you need to implement the EntityManagement client module like any other client module. Add the following project reference to the Application project:

XML
<ProjectReference Include="$(ServicesPath)EntityManagement/ITsynch.Suite.EntityManagement.ClientModule/ITsynch.Suite.EntityManagement.ClientModule.csproj" />

Then we add the dependency in our Suite module:

C#
1
2
3
4
builder.DependsOn<EntityManagementClientModule, EntityManagementClientModuleOptions>(opts =>
    {
        opts.SetDbContext<EquipmentsDbContext>();
    });

Next, add the Abstractions reference in the Domain project:

XML
<ProjectReference Include="$(ServicesPath)EntityManagement/ITsynch.Suite.EntityManagement.Abstractions/ITsynch.Suite.EntityManagement.Abstractions.csproj" />

Then we have to add EntityDescriptionView to our entity (e.g.: AuditEntity, EntitySharing, Discussion Topic, etc):

C#
public EntityDescriptionView? EntityDescription { get; set; }

We can also add PropertyDescriptionView if needed and makes sense to entity (e.g.: AuditLine).

C#
public PropertyDescriptionView? PropertyDescription { get; set; }

Hydrating entities

Now, in order to hydrate this entities we need to grab the description IDs from the descriptor client (backend) or the UI.

When we have the entity type available in the backend we can use the IEntityDescriptionProvider abstraction via dependency injection:

C#
1
2
3
4
5
// Method with type as a generic
var entityDescription = this.descriptionProvider.GetEntityDescription<Equipment>();

// Method with type as an argument
var entityDescription = this.descriptionProvider.GetEntityDescription(equipmentType);

Then, we can just send the description id (e.g: via MassTransit messages) to our consumer or saga in order to hydrate the description.

Note

In order to have this abstraction available we need to depend on the EntityDescriptorClient in our project and Suite module.

If the Entity Description IDs come from UI, we need to add the entity description id property to the GraphQL records and message:

C#
public Guid EntityDescriptionId { get; set; }

And in order to hydrate this property from the UI to send it to the backend, we need to import the EntityDescriptionIdPipe. You have to pass the entity well-known name configured in the descriptor to this pipe, and it returns an Observable with the entity description id. You can use it like this:

TypeScript
[entityDescriptionId]="equipmentWellKnownName | entityDescriptionId | async"

At this point we have the entity description id that came from the backend or the ui, and now we need to hydrate the entity description:

C#
entity.EntityDescription = await this.entityDescriptionRepository.FindByIdAsync(message.EntityDescriptionId) 
            ?? new EntityDescriptionView(message.EntityDescriptionId)

Remember to use the null-coalescing operator since the descriptor may have not been seeded to the client yet and we should be resilient. When the descriptor comes it just updates the empty entity description with all the data and we are ready to go. All of this should happen in a few seconds.