4

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?

Konrad
  • 1,529
  • 2
  • 17
  • 32

4 Answers4

2

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)
Armando Garza
  • 324
  • 1
  • 2
0

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

With great difficulty.

PUT, like DELETE and PATCH, have remote authoring semantics. "Make the resource look like this".

The basic idea being that I can take any HTTP aware editor that understands the media type, and use it to load a new representation, make changes to it, then store the changes -- without needing to know anything about how the origin server stores that information.

Thus, the burden is on the server to figure out how to convert the new representation into commands to send to its own backing storage.

One possible answer is to put more of the burden on your media type. Resources are allowed to have more than one representation, and you aren't required to support PUT for all of them. 415 Unsupported Media Type is there for you in the case where you need to reject a PUT that you can't convert into messages your domain model will understand.

That flow might look something like

GET /foo
Content-Type: application/json

And the consumer looking at this representation sees that they want to edit the resource. So it asks for the editable representation

GET /foo
Content-Type: application/vnd.hacky-workarounds.edit+json

PUT /foo
Content-Type: application/vnd.hacky-workarounds.edit+json

You might imagine a representation analogous to application/json-patch+json, with operations that match the command message that your domain model understands, and a query operations that describe the initial state (for instance, the hash of the current tree).

Depending on your schema, the client might be able to convert the original DTO to the editable representation on its own, without needing to perform a GET. HTTP is stateless, so the subsequent PUT should behave the same way regardless.

Another approach to consider is that the domain model has two responsibilities: making sure that state is internally consistent, and making sure that the transitions are valid. In other words, the origin server doesn't necessarily need to apply the business rules itself, but instead just validate that the rules have been applied correctly.

Thus, you check that the DTO is internally consistent, and you check that the transition from old state to new state is legal, and if both of those hold you just store the result without worrying about repeating the work yourself.

But the real answer is the one you hate - diff the two representations, reverse engineer the commands required to transform one into the other, and then apply those commands via the domain model.

PUT can be used to create resource, in addition to editing them (provided that you are comfortable with the notion that a client gets some control of the URI). So you could PUT a representation of a command message to some new resource. Likely you would want to use the If-None-Match header to ensure that particular URI is not already occupied. So each command would get a unique URI, selected by the client. What you don't get in that case is cache invalidation of the DTO (which has it's own URI already)

Allowing the client to select a URI for the the command message resource is analogous to allowing the client to select its own correlation identifiers.

VoiceOfUnreason
  • 32,131
  • 2
  • 42
  • 79
0

Like most design decisions the answer is "it depends".

Does updating a Foo usually require updating it's collection of Bar as well? Do they need to be updated in the same unit of work? Is Bar a value object? If the answer to one or more of these questions is "yes" then option 2 is probably the only course of action. Under these conditions you can consider FooDTO's Items collection to be a representation of the final state of Foo's Items collection at the completion of the unit of work. And since Bar is a value object, comparing them should be trivial.

If, on the other hand, Bar is an entity and has an identity (and potentially lifetime) of its own apart from Foo or it's expected that a Bar can be updated without needing a corresponding change to Foo then option 1 may be more suitable.

Another possibility is that a rich domain model is not the most appropriate architecture for this particular application. Evan's is quick to point out that DDD is a mindset and that there are plenty of applications that simply are not appropriate for a full implementation of of rich domain model, repositories, services, etc. That doesn't mean it won't benefit from domain modelling or establishing a ubiquitous language.

Kenneth Cochran
  • 1,355
  • 9
  • 16
-1

The problem is, you have Foo and FooDto. What is FooDto? Is that part of the "Domain"? It is probably a technical thing just because of some framework that needs it.

So you basically already have anemic objects, that is why you are struggling. You have to get rid of all anemic stuff to be able to really make a "rich" domain model work. You could just receive the full Foo on a PUT. You are the one defining how to deserialize from JSON (or whatever), so you could easily build the "right" object immediately, without any intermediaries, then you could use the "real" business methods on it immediately to make any request work.

Robert Bräutigam
  • 11,473
  • 1
  • 17
  • 36
  • no `FooDto` is not part of the domain. The only part of the domain in my example is `Foo`. `FooDto` is part of the API. `Foo` is not anemic. `FooDto` is a POCO(in DDD terms anemic) – Konrad Sep 07 '18 at 19:53