Search code examples
c#.netdomain-driven-design

Should handler from the Application layer return DTO or domain object


I have Domain/Application/Persistance and Api layer architecture.

  • Domain lives isolated without any reference to the outside layers
  • Application references Domain only
  • Persistance and Api layer are on the same level, referencing Application and Domain.

In the following example of api endpoint, where should I put input and output models. Input model as entry point of api endpoint and output model as exit point of api endpoint.

[HttpGet]
public async Task<IActionResult>Get(GetModel model)
{
    var queryModel = this.Mapper<QueryModel>(model);
    var getCarQuery = this.mediator.Send(queryModel);
}

Handler for this getCarsQuery lives on the Application layer. Inside this handler I use repository in order to get data from the database, here's where I have a dilemma:

Should handler from the Application layer return Dto (also stored in the Application layer) and having no mapping from dto to api response

[HttpGet]
public async Task<IActionResult>Get(GetModel model)
{
    var queryModel = this.Mapper<QueryModel>(model);
    CarDto getCarQuery = this.mediator.Send(queryModel);
    return Ok(getCarQuery);
}

Should handler return Domain object using repository and leave api side to handle mapping of domain object (from handler) to api response (dto)?

[HttpGet]
public async Task<IActionResult>Get(GetModel model)
{
    var queryModel = this.Mapper<QueryModel>(model);
    Car getCarQuery = this.mediator.Send(queryModel);
    CustomApiResponse response = _mapper<CustomApiResponse>(getCarQuery);
    return Ok(response);
}

Long story short, in the above architecture where would you put input and output models of the api endpoint and should handler return dto or data models?


Solution

  • ItDepends™

    If you don't use a DTO, and your domain models ever change, suddenly that change propagates all the way up to your API, and it could be in undesirable ways:

    • Removed properties that could have been replaced with sane defaults are now just missing from API responses.
    • Renamed properties are renamed from your responses, breaking compatibility.
    • New properties containing sensitive info are suddenly exposed where you don't want them to be.

    Most newer programmers think this kind of code reuse is good, but it can often shoot you in the foot. Instead, if each layer has its own set of surface models, you can confine these changes to be only internal and allow for more flexibility when changing things.

    On the flip side, if you use fewer models and share them throughout your layers, you then need to manually propagate changes all the way up through each layer and their models -- this is an overhead overhead.

    In short, it depends on the size of your app, the ways in which you expect things to change, and what you care about more.

    I personally would prefer the latter option and just eat the overhead, than plonking myself into a wet cement-filled hole, but of course that's just me and just happens to work best for my projects.