C# – Mapping API models(DTOs) to rich domain models

asp.net-corecdomain-driven-designrestweb-api

How to implement HTTP's PUT that works with child collections when using DDD's rich domain models?

Let's say we've got an aggregate root with a nicely encapsulated collection of items:

(I omitted persistence specific properties like Id for brevity)

public class Foo : IAggregateRoot
{
    private readonly List<Bar> _items = new List<Bar>();

    public IReadOnlyCollection<Bar> Items => _items;

    public void AddItem(Bar bar)
    {
        _items.Add(bar);
    }

    public void RemoveItem(Bar bar)
    {
        _items.Remove(bar);
    }
}

Now we want to implement PUT /foos/{id} where you pass DTO like:

public class FooDto
{
    public IEnumerable<BarDto> Items { get; set }
}

Now the problem is we can't simply map FooDto to Foo. It becomes a complex problem to solve, especially when you want to remove some Bar from Items.

We're left with two options:

  • Create 2 separate routes for adding and removing items, like POST /foos/{id}/items and DELETE /foos/{fooId}/items/{itemId}
  • Write some twisted logic to compare changes in collection and based on that deduce what was added and removed and call AddItem or RemoveItem respectively

Is there anything I am missing here or doing wrong? Would I be better off using anemic domain models to simplify implementation of Web API? That would mean Web API would dictate how my domain models should look like and I think it's a bit wrong.

A similar question I've found while looking for solutions: Do RESTful APIs tend to encourage anemic domain models?

Best Answer

I wouldn't consider your option #2 as "twisted logic".

You just need an equality function (something to determine if two Bars are the same) and then:

toAdd = elements in newItems that don't exist in currentItems
toDelete = elements currentItems that don't exist in newItems

I don't know C#, but in pseudo-code could be something like this:

for e in newItems:
    if not e in currentItems:
        currentItems.add(e)

for e in currentItems:
    if not e in newItems:
        currentItems.remove(e)
Related Topic