Skip to content

Chapter 5: Entities, Domain Events and Policies

Entities

Most interactors call one or more methods on the entities. These entities are your “business objects” and the methods implement the core of your “business logic” in a clean object-oriented fashion.

An entities method should only to things on the local entity. If must not depend on other entities (aka aggregates). It may use other services that get pass into the method using a parameter. But this often is also a sign of a code-smell. For such tasks we will use domain-events and policies (see below).

In the above sample the following line is the meat:

logbookEntry.Publish();

The LogbookEntry entity looks as following.

public class LogbookEntry : EntityBase
{
    ...

    public bool IsPublished { get; internal set; }

    ...

    public void Publish()
    {
        if (IsPublished) return;

        IsPublished = true;
        RaiseDomainEvent(new LogbookEntryPublishedEvent(Id));
    }

    ...
}

See full source-code.

Notes:

  • The Publish() method first checks if the LogbookEntry is already published because then nothing needs to be done anymore. If not the IsPublished flag will be set and a domain event LogbookEntryPublishedEvent will be raised to indicate what just happened to other parts of the application (more on this later).

  • The IsPublished as an internal setter. This is to simplify writing unit-tests. My unit-test assembly is listed as a so called friendly assembly to the domain assembly so internals are visible to unit-tests too.

  • The EntityBase base-class features the `Ìd“ property as well as the storage of the uncommitted domain-events. See here for soure-code.

Domain Events

A domain event (see DDD) indicates that something important happened on a entity (aka domain aggregate). They are where the open/close principals gets into the play. Some call them extensibility points. It’s the way entities can communicate with the outer world.

The domain event LogbookEntryPublishEvent gets broadcasted using MediatR after the data-access did persist the entitiy. Because they are broadcasted to the in-process messaging zero to many subscribers can consume them and do various others things. Normally these things are not so closely related to the previous domain entity logic or need to interact with more then just the one current entity.

In our example the domain event LogbookEntryPublishedEventlooks like this:

public class LogbookEntryPublishedEvent : DomainEventBase
{
    public Guid LogbookEntryId { get; }

    public LogbookEntryPublishedEvent(Guid logbookEntryId)
    {
        LogbookEntryId = logbookEntryId;
    }
}

As you may have noted I have a baseclass for my domain events call DomainEventBase. This just inherit from INotification records the time when the event happened:

public abstract class DomainEventBase : INotification
{
    public DateTime DateOccurred { get; protected set; } = DateTime.UtcNow;
}

In a event-sourced application the donain-event would then be stored in the event-store and projectors will listen on then to to update their read-stores (eg. SQL tables). In my application I do not do event-sourcing so the domain-events serve only for notifying other building blocks that can do further processing. Policies are such building building blocks.

After we have the interactors in place which do the “dance of the entities” as well as having the entities with the business logic and the domain-event it’s time to move on what happens after the domain-events are published.

Policies

Policies are application-logic classes (in the application layer) that listen to domain-events. Policy then can do additional work that is not directly part to the previous domain method but to its result (the domain-event). The do work with other services or entities/aggregates outside of the initial entity/aggregate. Entities/aggregates should depend on other entities/aggregates.

To do their stuff Policies can access all of the application- and domain-level infrastructure – just like interactor can. This means they can load and update domain entities using repository-interfaces, call domain entity methods to execute other business-logic or they can use other application level services.

In our sample for publishing logbook entries we have two policies in place that listen to the LogbookEntryPublishedEvent domain-event.

The first one is LogTelemetryLogbookEntryPublishedPolicy which does only one little thing: it uses the ITelemetryService application-service to record the publish use-case as a telemetry-event. In my case they get submitted to Azure AppInsights for further logging and analytics.

The second policy is a little bit more interesting: PublishNewLogbookEntryNotificationPolicy. The job of the policy is to publish Notification‘. Notifications – in my domain – means a notification to users about something that happened. These notifications get picked up later on and an email newsletter is generated and sent every user.

Let’s take a look at this policy:

[UsedImplicitly]
public class PublishNewLogbookEntryNotificationPolicy : INotificationHandler<LogbookEntryPublishedEvent>
{
    [NotNull] private readonly IDiverRepository diverRepository;
    [NotNull] private readonly ILogbookEntryRepository logbookEntryRepository;
    [NotNull] private readonly INotificationPublisher notificationPublisher;

    public PublishNewLogbookEntryNotificationPolicy(
        [NotNull] IDiverRepository diverRepository,
        [NotNull] ILogbookEntryRepository logbookEntryRepository,
        [NotNull] INotificationPublisher notificationPublisher)
    {
        this.diverRepository = diverRepository ?? throw new ArgumentNullException(nameof(diverRepository));
        this.logbookEntryRepository = logbookEntryRepository ?? throw new ArgumentNullException(nameof(logbookEntryRepository));
        this.notificationPublisher = notificationPublisher ?? throw new ArgumentNullException(nameof(notificationPublisher));
    }

    public async Task Handle([NotNull] LogbookEntryPublishedEvent notification, CancellationToken cancellationToken)
    {
        if (notification == null) throw new ArgumentNullException(nameof(notification));

        var recipients = await diverRepository.GetAllTauchboldeUsersAsync();
        var logbookEntry = await logbookEntryRepository.FindByIdAsync(notification.LogbookEntryId);
        var author = await diverRepository.FindByIdAsync(logbookEntry.OriginalAuthorId);
        var message = $"Neuer Logbucheintrag '{logbookEntry.Title}' von {author.Realname}.";

        await notificationPublisher.PublishAsync(
            NotificationType.NewLogbookEntry,
            message,
            recipients,
            relatedLogbookEntryId: notification.LogbookEntryId);
    }
}

Notes:

  • The policy uses the IDiverRepository and ILogbookEntryRepository for accessing the entities it needs.

  • The policy gets all the data it needs in the notification parameter which is the domain-event. In this sample it gets the notification.LogbookEntryId which it uses to load details about the LogbookEntry from the database.

  • The INotificationPublisher.PublishAsync() is used to publish the notification to all interested users. Note that INotificationPublisher is general application-service which gets injected into the policy as a constructor parameter.

After we set up the basic stuff its time to move to the most tricky part to fiddle out how to do it: Presenters and View-Models.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: