I’m looking at using hexagonal/ports & adapters design in some code I’m working on and for the most part I’m happy but there is one part that I’m struggling with which how to handle the need for different return values from a single call.
I’ll use an example to better illustrate, this is pseudo code so names aren’t represntative of real code.
If I have a domain “service” which has a method AddEntity I can have 3 returns:
- Newly added object
- Invalid Data
- Entity already exists
The adapter that uses this “service” will then take the return and will send the data in the object back (it may transform it to a “view model” first).
In C# the adapter maybe a WebApi controller with code something like this:
public IHttpActionResult Post(DataViewModel entityData)
{
var item = this.service.AddEntity(entityData.Name, entityData.Description);
if (item == null)
{
return this.StatusCode(HttpStatusCode.Conflict);
}
var viewModel = Mapper.Map<EntityDTO, DataViewModel>(item);
return this.Created(this.Url.Link("DefaultApi", new { controller = "Entity", id = viewModel.Id }), viewModel);
}
The problem as you can see is at the moment it can only deal with 2 returns, either it gets the entityDTO which it maps and returns to the client or it gets null and just returns a Conflict HttpStatus code.
What I’m trying to work out is how I could get back to the adapter, controller in example above, is either the object or one of the two messages.
The approaches I’ve considered are:
- extending the entityDTO class to have additional information which would allow me to pass something indicating a problem (an enum maybe?)
- raising typed exceptions and handling the exceptions
- raising events
if I used the enum then the controller code would look something like this:
public IHttpActionResult Post(DataViewModel entityData)
{
var item = this.service.AddEntity(entityData.Name, entityData.Description);
if(item.result == Result.Conflict)
{
return this.StatusCode(HttpStatusCode.Conflict);
}
if (item.result == Result.Invalid)
{
return this.StatusCode(HttpStatusCode.Invalid);
}
var viewModel = Mapper.Map<EntityDTO, DataViewModel>(item);
return this.Created(this.Url.Link("DefaultApi", new { controller = "Entity", id = viewModel.Id }), viewModel);
}
Of these extending the DTO seems the least bad option but I’m unsure if I’ve just missed something in the pattern which gives an easy way to do this.
Is there any specific way to do this I’ve missed? or is one of the options for sorting this I mentioned above a better way than the others?
Communicating errors is not part of the responsibility of a DTO, so adding a field to the DTO for that purpose should really be a last-resort option.
The typical solutions for the problem where you return either an object or one out of a set of errors are:
- Returning a Variant record that holds either the object or the error code,
- Returning a tuple of object and error code,
- Returning an error code (with a special value for “no error”), and using an out-parameter for the object,
- Returning the object and using an out-parameter for the error code,
- Returning the object and using an exception to communicate the error.
Which option you choose depends in part on the possibilities and culture around the language you use. The option with the Variant record probably communicates the most clearly that you get either an object or an error.
There are a couple of principles to bear in mind.
The first is command-query separation here (CQS). Either you are updating, or you are querying, but not both, because this creates an issue of side-effects. Adding a new X should be a write operation, and so not return a value.
The pragmatic issue here is that you might not be able to use a Guid or Hi-lo algorithm to create the identity for the X you are about to add. In that case you need to either provide a return type, or add an out parameter to retrieve it. That should be the Id not the instance.
In this case, with a hierarchical architecture style, I might use a command pattern anyway and consider adding a property on the command that can be updated for the created id if required. That’s essentially a hack to get around an inability to create the id here. See my discussion here: http://iancooper.github.io/Paramore/CommandsCommandDispatcherandProcessor.html
Then I would use some kind of retrieve to return the instance of X if I needed it for my response i.e. a separate query. Again, I would not return the instance of X on the command or as an out parameter or return type. That minimizes your exposure to side-effects if you have to do this.
When considering error codes vs exceptions, bear in mind that an error code is effectively a query – what was the state of that operation? In many cases you could perform the command and then run the query to determine the result. For example if it is common that folks will try to recreate an existing X, query for it before the operation happens can be an alternative to throwing an exception.
Once you understand our reads and writes as all using a port layer to a model, you can appreciate calling either of them from your adapter layer.
In other cases think about the invariants you stating – the pre-conditions and the post-conditions.
- There is not already an X.
- X was created successfully
If you violate those conditions, I would throw an exception, to indicate that one of the invariants has failed.
1
This is actually a great question. Your proposal or Bart Van Ingen Schenau’s approaches all should work equally well. Returning tuples or a new data structure (like enum) will make your controller happy.
I guess the important thing here (and what you are asking) is how this should work in the Hexagonal Architecture. You really want to avoid returning a technical result in your ports; your ports should be technology-free. I know that the example is not from your actual system but having methods such as addEntity()
is really not recommended in your ports. Similarly with return values, try returning your 3 outcomes in an enum but make sure that your hexagon names appropriately.