0

I have a Document class which needs to be authorized externally before being saved to database.

Is it ok to create an authorize method like ...

class Document:
    authorize(IExternalAuthorizator authorizator):
        authorization_result = authorizator.authorize(this)
        // update internal state with result...

and then using it in a use case or service like ...

class UseCase:
    execute(IDocumentRepository repo, IExternalAuthorizator authorizator):
        doc = new Document()
        doc.authorize(authorizator)
        repo.save(doc)

or should I have the Document class like this ...

class Document:
    authorize(AuthorizationResult result):
        // update internal state with result...

and then the use case like ...

class UseCase:
    execute(IDocumentRepository repo, IExternalAuthorizator authorizator):
        doc = new Document()
        result = authorizator.authorize(doc)
        doc.authorize(result)
        repo.save(doc)

or none of them but a third option?

Any help?

k-ter
  • 737
  • 1
  • 5
  • 15

1 Answers1

2

Based on my own research of the literature

  • The second form is better
  • The difference between the two is small.

Your domain model is essentially a state machine: it has some information of its own, new information arrives, and the model decides how to integrate the new information with what it already knows.

The introduction of additional ports typically occurs when there is some additional bit of information that we need in order for the model to proceed, and that information is somewhere else.

The problem with information that is somewhere else is that the information may not be available at the moment that we want it (perhaps Authorization is out of service for an upgrade).

So a logical question to ask is: which of these interfaces is a better fit for asynchronous information exchange?

Certainly, if we went to a bunch of effort to isolate our domain model from our persistence solution to avoid cluttering the code, then we should also be isolating our domain model from asynchronous message retrieval for same reason. That code is part of the world of plumbing, and not at all relevant when we are writing our domain computations.

In effect, letting the application worry about negotiating with Authorization means that, when authorization is unavailable, the application can continue to process other work, and come back to this particular document when the authorization data becomes available. The domain model itself doesn't (and shouldn't) have the context to do that.

Expressing the same idea a different way, the "single responsibility" of the domain model is bookkeeping, not information retrieval.

Also notice that the two approaches are not necessarily too different:

class Document:
    authorize(IExternalAuthorizator authorizator):
        authorization_result = authorizator.authorize(this)
        this.authorize(authorization_result)

    authorize(AuthorizationResult result):
        // update internal state with result...

or

class UseCase:
    execute(IDocumentRepository repo, IExternalAuthorizator authorizator):
        doc = new Document()
        authorize(doc, authorizator)
        repo.save(doc)

    authorize(Document doc, IExternalAuthorizator authorizator):
        authorization_result = authorizator.authorize(doc)
        doc.authorize(authorization_result)

In short: it's all the same in both cases except for the little data retrieval piece. That won't make a difference when the data retrieval is trivial, but it will make a difference as more and more plumbing concerns get introduced.

VoiceOfUnreason
  • 40,245
  • 4
  • 34
  • 73