14

I'm reading Vaughn Vernon Book - Implementing Domain Driven Design. There is an example of a Project Management Application. There are aggregates like BacklogItem, Sprint, etc. If I have BacklogItemNotFoundException defined in Domain layer. Should my Rest adapter catch it and transform into NotFoundHttpResult? Or any other broken invariant exceptions like: EmailPatternBrokenException or TooManyCharactersForNameException or whatever should be handled in Rest adapter(ports&adapters architecture) and re-transformed into rest responses? If yes, it means that RestAdapter should have a reference to Domain layer? This is what bothers me...

DmitriBodiu
  • 887
  • 6
  • 19
  • 2
    This Q is an overt call to heated "where to validate?" and "exception vs simple return value" side discussions :) – guillaume31 Aug 21 '18 at 11:36

5 Answers5

11

The question is a contradiction. If it is a Domain Exception, it means that it is thrown by the domain.

Anyway, exceptions thrown by the domain should be handled by the application layer.

I have an exception handler decorator for the command bus, that catch any domain exception and translates it into an Application Exception.

This application exception is thrown to the adapters.

Adapters know about application exceptions, not domain exceptions.

UPDATE

My domain exception is an abstract base class from which the concrte domain exceptions inherit

public abstract class DomainException extends RuntimeException {

private static final long serialVersionUID = 1L;

private ErrorMessage mainErrorMessage;
private List<ErrorMessage> detailErrorMessages;

protected DomainException ( List<ErrorMessage> aDetailMessages, Object... aMainMessageArgs ) {
    this.mainErrorMessage = new ErrorMessage(this.getClass().getSimpleName(), aMainMessageArgs );
    this.detailErrorMessages = ( (aDetailMessages==null) ? new ArrayList<ErrorMessage>() : aDetailMessages );
}

public ErrorMessage mainErrorMessage() {
    return this.mainErrorMessage;
}

public List<ErrorMessage> detailErrorMessages() {
    return this.detailErrorMessages;
}
}

ErrorMessage has a key and a list of args. The messages are in a property file where the key is the name of the concrete domain exception class.

Application exception is just one type, which holds the concrete text message.

public class ApplicationException extends Exception {

private static final long serialVersionUID = 1L;


private String mainMessage;
private String[] detailMessages = new String[0];


public ApplicationException ( String aMainMessage, Throwable aCause, String... aDetailMessages ) {
    super ("Main Message = "+aMainMessage+" - DetailMessages = "+Utils.toString(aDetailMessages), aCause );
    this.mainMessage = aMainMessage;
    this.detailMessages = ( (aDetailMessages==null) ? (new String[0]) : aDetailMessages );
}


public String mainMessage() {
    return this.mainMessage;
}

public boolean hasDetailMessages() {
    return (this.detailMessages.length > 0);
}

public String[] detailMessages() {
    return this.detailMessages;
}
}

I have a decorator (wraps the execution of every command) for handling domain exceptions:

public class DomainExceptionHandlerDecorator extends Decorator {

private final DomainExceptionHandler domainExceptionHandler;


public DomainExceptionHandlerDecorator (DomainExceptionHandler domainExceptionHandler) {
    this.domainExceptionHandler = domainExceptionHandler;
}


@Override
public <C extends Command> void decorateCommand(Mediator mediator, C command) throws ApplicationException {
    try {
        mediator.executeCommand(command);
    } catch ( DomainException de ) {
        this.domainExceptionHandler.handle (de);
    }
}
}

And I have a domain exception handler that takes a domain exception, translates it into an app exception by reading properties file (TextMessageService does the job) and throw the app exception.

public class TranslatorDomainExceptionHandler implements DomainExceptionHandler {

private final TextMessageService configurationService;

public TranslatorDomainExceptionHandler ( TextMessageService aConfigurationService ) {
    this.configurationService = aConfigurationService;
}

@Override
public void handle ( DomainException de ) throws ApplicationException {

    ErrorMessage mainErrorMessage = de.mainErrorMessage();
    List<ErrorMessage> detailErrorMessages = de.detailErrorMessages();

    String mainMessage = this.configurationService.mensajeDeError ( mainErrorMessage );

    String[] detailMessages = new String [ detailErrorMessages.size() ];

    int i = 0;
    for ( ErrorMessage aDetailErrorMessage : detailErrorMessages ) {
        detailMessages[i] = this.configurationService.mensajeDeError ( aDetailErrorMessage );
        i++;
    }
    throw new ApplicationException ( mainMessage, de, detailMessages);      
}
}

The adapter (an UI for example) will catch the app exception and show its message to the user. But it doesn't know about domain exceptions.

choquero70
  • 3,332
  • 2
  • 24
  • 40
  • Sounds nice. Could you share a code example how this mapping is performed? Do you catch any Domain Exception and map it to ApplicationException or you create a separate exception class per domain exception? – DmitriBodiu Sep 04 '18 at 14:53
  • @DmitriBodiu I've updated my answer explaining how I do it and showing some code – choquero70 Sep 04 '18 at 16:57
  • Thanks a lot for your example! I have one question. How would you handle a scenario when your repository throws UserNotFoundException and you should map it to HttpNotFoundResult? – DmitriBodiu Sep 05 '18 at 05:38
  • @DmitriBodiu I handle it like any other domain exception. The translator maps UserNotFoundException to an ApplicationException object with the message of the UserNotFoundException read from the properties file. The client (the UI for example) just executes a command (app service), catch the ApplicationException and shows the message to the user. I don't have different kinds of app exceptions like HttpNotFoundResult extending AplicationException, but you could if you want. In the translator you should ask if the domain exception is UserNotFoundException and if so throw HttpNotFoundResult – choquero70 Sep 05 '18 at 06:28
7

I try to avoid domain exceptions as much as I can and prefer to make invalid states unreachable instead. The first reason is that exceptions are for exceptional, unexpected things, the second that I don't like my code to be cluttered with fine-grained try/catches for every little business-ish thing that could go wrong.

BacklogItemNotFoundException

To me this is typically your Repository or query service returning null or an empty list. No need for a domain exception.

EmailPatternBrokenException

TooManyCharactersForNameException

I let the validation feature of my web framework handle these. You could also check it in the Domain but it will rarely reach that point and you don't really need to handle that kind of error specifically.

As a result, the two typical scenarios are:

+-----------------------+--------------------+-------------------------------------------------+
| Domain                | Application        | Presentation                                    |
+-----------------------+--------------------+-------------------------------------------------+
| Expected failure case | Return Result.Fail | Clean error message                             |
+-----------------------+--------------------+-------------------------------------------------+
| Exception             | -                  | Caught in catch-all clause > 500 error or other |
+-----------------------+--------------------+-------------------------------------------------+
Community
  • 1
  • 1
guillaume31
  • 12,725
  • 28
  • 43
  • 1
    So you aren't a fan of value objects for validation or you'd have duplicate validation, in the domain and in the UI/Application layer? I usually duplicate validation logic, where I use frameworks in the UI (multi-error reporting, etc.) and exceptions in the domain (such values are unexpected to reach this point). – plalx Aug 21 '18 at 16:59
  • 1
    This answer covers it. I would add that front-end validations are a convenience and should prevent the common/typical errors but the domain is the true authority and if those rules are broken they should be bubbled up. – Eben Roux Aug 22 '18 at 06:19
  • @plalx I usually don't duplicate, mostly because in cases where the question arises, the limit between user input validity and domain validity is generally blurry. For instance, is "user name <= 100 chars" a domain rule or really a technical rule? In doubt, I prefer to validate only on the UI/Application side. But yes, I can see duplicate validation being beneficial where it is justified. – guillaume31 Aug 22 '18 at 08:00
  • *"prefer to make invalid states unreachable"* What did you mean by that - what technique do you use to achieve that? – lonix May 26 '19 at 14:34
  • @Ionix I mean unreachable at runtime because directly encoded in types. If your language allows it, having the [type system](https://fsharpforfunandprofit.com/posts/designing-with-types-making-illegal-states-unrepresentable/) participate in enforcing domain invariants is a good idea. – guillaume31 May 27 '19 at 16:35
7

I will add my 2 cents about error handling, not specifically related to DDD.

The exception are part of the contract you expose to the consumer. If you're expected to for example add an item to a shopping cart, the exception you may explicitly throw include itemNotAvailable, shoppingCartNotExisting, etc...

Technical exception on the other hand are not part of the contract, they may occurs but shouldn't be explicitly handled as no one can do anything about it, they must imply the operation interruption (and the rollback of the current unit of work).

A rest interface is a contract for an operation on a resource. When using rest over http the terms of the contract are related to the http protocol.

Typical operation described above (adding ie. post an item on a cart resource) would be translated to, for example, 404 for shoppingCartNotExisting and 409 for itemNotAvailable (conflict ie. the update on the resource is no more possible because some state has changed meantime).

So yes all "domain" exception (expected exceptions as part of the contract) should be explicitly mapped by the rest adapter, all unchecked ones should result in a 500 error.

Gab
  • 7,071
  • 2
  • 32
  • 62
3

TLDR; It is OK if the Application or Presentation layer has a dependency to the Domain layer, the other way is not recommended.

Idealy, there should not exist any dependency from one layer to another but that is impossible or the software would not be usable. Instead you should try to minimize the number and the direction of the dependencies. The general rule or best practice to a clean architecture is to keep the Domain layer agnostic of the infrastructure or the Application layer. The Domain objects (Aggregates, Value objects etc) should not care about a specific persistence or Rest or HTTP or MVC, just like the domain experts don't care about these things.

In real world, the Domain layer may be influenced by technology (like frameworks). For example we put annotations to mark some Domain objects as behaving in some specific way when persisted instead of using external XML or JSON files just because it is at hand, it is easier to maintain them. We need, however, to limit these influences to a minimum.

Constantin Galbenu
  • 14,628
  • 3
  • 23
  • 41
1

The application layer is business-specific domain itself. So your application layer should handle the domain exception based on what the application/business expects. The application(eg. client facing web application, mobile, an internal CRM app, or a backend-for-frontend API) is probably not the only client of the domain layer(eg. a rest api, a jar library). There might be certain domain exceptions that you don't want to expose to the end-user so the application has to wrap these exceptions specifically or handle exceptions globally.

alltej
  • 5,306
  • 4
  • 34
  • 65