Skip to content

Chapter 4: Use-Cases & Interactors

In my architecture the use-case command objects get served by so called Use-Case Interactors. Technically these are MediatR ÌRequestHandler which handle the use-case commands. Interactors orchestrate “the dance of the entities” as Uncle Bob says. This means that they do a mix of loading and saving entities from/to the persistence layer, call business logic using the entitiy-methods, calling other services, connect to external systems etc. They are orchestrators or moderators if you wish. Its one of the main parts for the application layer.

I you look at solution explorer you immediately see what use-cases this application addresses. This way even the business and domain-exports understand the scope of the application. This is one of the really cool things.

Use-Case Structure

So let’s jump to a sample code of the use-case interactor which publishes a LogbookEntry for public viewing:

[UsedImplicitly]
internal class PublishLogbookEntryInteractor : IRequestHandler<PublishLogbookEntry, bool>
{
    [NotNull] private readonly ILogger<PublishLogbookEntryInteractor> logger;
    [NotNull] private readonly ILogbookEntryRepository dataAccess;

    public PublishLogbookEntryInteractor(
        [NotNull] ILogger<PublishLogbookEntryInteractor> logger,
        [NotNull] ILogbookEntryRepository dataAccess)
    {
        this.dataAccess = dataAccess ?? throw new ArgumentNullException(nameof(dataAccess));
        this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }

    public async Task<bool> Handle([NotNull] PublishLogbookEntry request, CancellationToken cancellationToken)
    {
        if (request == null) throw new ArgumentNullException(nameof(request));

        logger.LogInformation("Publish Logbook-Entry {logbookEntryId}", request.LogbookEntryId);

        var logbookEntry = await dataAccess.FindByIdAsync(request.LogbookEntryId);
        if (logbookEntry == null)
        {
            logger.LogError("Logbook-Entry {logbookEntryId} not found", request.LogbookEntryId);
            return false;
        }

        try
        {
            logbookEntry.Publish();
            await dataAccess.UpdateAsync(logbookEntry);

            logger.LogInformation("Logbook-Entry {logbookEntryId} published successfull.", request.LogbookEntryId);
            return true;
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Error publishing logbook entry {logbookEntryId} {logbookEntryTitle}", logbookEntry.Id, logbookEntry.Title);
            return false;
        }
    }
}

Notes:

  • The interactor implements ÌRequestHandler so MediatR use them to handle PublishLogbookEntry request that return a bool result.

  • Use private readonly fields that can be set only within the constructor of the class and therefore are immutable.

  • The constructor parameters normally get injected by IoC container but in the unit-tests fakes are provided for them (see Dependency Inversion principal).

  • The constructor implementation uses C# 7 throw expressions to check if the value is not null. I also use R# annotations like [NotNull[] and [UsedImplicitly]. These annotations help R# to help you by hinting where you might get a null-reference exception if you do not check for null.

  • Another thing I got from Clean Architecture is to make as much as possible accessible only internal. Everything that I declare public is considered an external API that needs to be supported and therefore can not easily change. When everything in a assembly is public its a sign of a code-smell. Regarding Uncle Bob hiding things by not making them public is the biggest advantage for object-oriented programming (OOP).

Now that we have seen the application layer – or Application Business Rules as it is called CA – its time to go to the Enterprise Business Rules. Let’s look at the Entities and Domain Events here …

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 )

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: