If business logic wants to send an email

Viewed 1904

I'm trying to implement Uncle Bob's Clean Architecture:

Uncle Bob's Clean Architecture

Everything is ok except the UseCases/Interactors. I need to register a user, so I need RegisterUser UseCase. In the concrete interactor I implement the RegisterUser use case which just create User entity and hash its password with PasswordBroker port.

After user creation I need to send a verification email. To do this I need to use framework's component. And there are two main problems.

  1. I think it has no sense to write port for the Mail service, because this port would be just a huge copy/paste abstraction of a framework service
  2. Interactor cannot send an email after user creation, because User finally would be stored after Doctrine flush() inside controller. There is a risk, when we've sent an email, but User wasn't stored in a DB. It must be consistent.

What's the best approach in this case?

I think we need some kind of port that relates to the concrete use case, this port will be called in the end of use case and implemented in Application Layer where framework's Mailer and Doctrine are available.

2 Answers
  1. I think it has no sense to write port for the Mail service, because this port would be just a huge copy/paste abstraction of a framework service

I would create a Notification interface that I pass to the use case interactor, similar to an EntityGateway or EntityRepository.

I would design the Notification interface in a way that it abstracts from the concrete notification mechanism, e.g. e-mail, messanger, etc.

The Notification could then look like this:

public interface Notification {
    public void notifyUserRegistered(User user);
}

The implementation would be placed in an outer layer, usually the interface adapters layer. Because this is the layer where interfaces of inner layers gets adapted - therefore the name.

Such a Notification interface can be easily mocked in a test and this keeps your tests fast. Thus I don't see the notification interface as a huge copy/paste abstraction.

  1. Interactor cannot send an email after user creation, because User finally would be stored after Doctrine flush() inside controller. There is a risk, when we've sent an email, but User wasn't stored in a DB. It must be consistent.

First I guess you can't enforce both in anyway, because sending an e-mail does not participate in the db transaction. Thus you will still have the consistency problem.

But you usually can register with the active transaction so that you get informed when it completes successfully. This means that the Notification implementation would just register a callback that gets invoked when the transaction finished. Then you can send the e-mail.

I don't know how it can be done in php, maybe that is another question here on SO. In Java there are several ways depending on the transaction api you use.

For Spring take a look at Transaction bound events or JEE's TransactionSynchronizationRegistry. Just to mention some if Java developers read this.

This is the upside/downside to using frameworks. Clean architecture intentionally isolates you from dependencies like this. This allows things to change out from under you without being affected by that change. It also allows you to test against an abstraction (writing automated tests against framework code invariably is horrible).

What you need to do is decide whether it is worth compromising the clean architecture to make use of the framework's advantage. It's a balancing act, and one that we all have to make decisions on a case-by-case basis. The more a framework does for us, the less control we get, and generally the less architecturally "pure" our application ends up. But, without frameworks, we have to write code that is essentially wasteful duplication of existing functionality.

Related