Skip to content

Activation Module

The activation module allows to configure certain entities to be activated or deactivated according to business necessities.

Integration

Depending on the ActivationModule will automatically register all required dependencies for filtering out inactive entities (as well as the ones required to opt out from this behavior). An entity can be made deactivatable by implementing the corresponding interface

C#
1
2
3
4
5
6
7
public class MyModule : SuiteModule
{
    public override void SetupModule(IModuleBuilder builder)
    {
        builder.DependsOn<ActivationModule>();
    }
}
C#
1
2
3
4
5
6
public class Reason : IDeactivatableEntity
{
    public Guid CorrelationId { get; set; }

    public bool IsActive { get; set; } = true;
}

Important

Remember to initialize IsActive field to true unless you want all new instances of your entity to be created as inactive ones.

The handling of this field is mostly up to the implementor to decide, we only expose a utility method SetActiveStatus(bool isActive). The module will automatically decorate the newly added field in your entity's class mapping.

Filtering

Inactive entities will be filtered out by default. Of course, activation filters out entities via a global filter specification, which are not applied when querying via FindByIdAsync method(or any of its derivatives).

This behavior can be overridden if needed by injecting the IActivationContextManager.

C#
public class MyService : IService
{
    private readonly IActivationContextManager activationContextManager;
    private readonly IReadOnlyRepository<Guid, Entity> repository;

    public MyService(
        IActivationContextManager activationContextManager,
        IReadOnlyRepository<Guid, Entity> repository)
    {
        this.activationContextManager = activationContextManager;
        this.repository = repository;

    }

    public async Task DoSomething()
    {
        await activationContextManager.WithActivationFilterDisabledAsync(
            () => await this.repository.AllAsync();
        );
    }
}

Keeping views in sync

When a service holds a read model (view) that projects an entity owned by another service, that view needs to mirror the source entity's activation state. The module handles this automatically for any class that implements IDeactivatableView.

C#
public interface IDeactivatableView : ICanBeDeactivated, ICorrelatedEntity;

Implement this interface on your view class and register it with the entity descriptor, linking it to the source entity's well-known name:

C#
1
2
3
4
5
6
7
8
public class ReasonView : IDeactivatableView
{
    public Guid CorrelationId { get; set; }

    public bool IsActive { get; set; } = true;

    // ... other projected fields
}
C#
1
2
3
4
5
6
7
8
9
public class ReasonViewDescriptor : IEntityDescriptor<ReasonView>
{
    public void Describe(EntityDescriptorBuilder<ReasonView> builder)
    {
        builder.DescribeView(
            sourceEntityWellKnownName: "Reasons_Reason",
            viewWellKnownName: "MyService_ReasonView");
    }
}

At startup the module scans all loaded assemblies for concrete types implementing IDeactivatableView and automatically registers a MassTransit consumer for each one. When an EntityActivationChanged message arrives with a routing key matching the view's source entity well-known name, the consumer fetches the corresponding view record (bypassing the activation filter so inactive views are also reachable) and updates its IsActive field.

Important

The view's entity descriptor must declare both well-known names via DescribeView. The view's own well-known name is used for the queue name ({view-wkn}-activation-changed), while the source entity's well-known name is used as the direct routing key to receive EntityActivationChanged messages. This ensures each view gets its own dedicated queue even when multiple views across different services project the same source entity.

Opting out of automatic view consumer registration

In rare cases the same process loads an assembly that contains IDeactivatableView types but the module context has not registered descriptors for them (this can happen in shared test assemblies or unconventional module compositions). In that situation the consumer definition will fail at bus startup because there is no entity description to derive the routing key from. Call DisableViewConsumers() to skip the scan for that specific context:

C#
1
2
3
4
5
6
7
8
public class MyModule : SuiteModule
{
    public override void SetupModule(IModuleBuilder builder)
    {
        builder.DependsOn<ActivationModule, ActivationModuleOptions>(
            opts => opts.DisableViewConsumers());
    }
}