0

I have an Agenda that holds many Card, and a Card has multiple DetailItem that hold a value like Email, Phone and a Label

So, I can do:

agenda = new Agenda()

oneCard = new Card()
item = new DetailItem(new Email("x@y.z"), new Label("Work")
oneCard.addItem(item)

agenda.addCard(oneCard)

By rule, there can only be one DetailItem with an Email instance with "x@y.z" value, so if you try to add a new item with that data it will raise an exception.

This seems to be ok until I try to update a DetailItem. I can't find a way that I feel comfortable with.

I try to think in terms of business model and not implementation details, but I cannot leave them off for a long, and they enter the domain I like it or not.

The thing is that I will have a REST interface, and I have two ways of doing things.

  1. Send a PUT to /cards/<cardId> with a detailItems' array, fetch the Cardby ID, create a newCardwith the new data provided in the PUT, and sync the currentCard` with the new one.
  2. Send a PUT to /cards//items/, fetch the Card, find the DetailItem, and update it the same way in option 1

If I go with option 1, I have to remove all DetailItem from current Card that don't exists in the new one. That leads with some INSERT queries produced by the ORM, and some DELETES. No UPDATE at all.

If I go with option 2, I will have many PUTs to change multiple items, which is not performant at all, and it will lead me to introduce an ID field to the DetailItem so I can identify them, which introduces something is not part of the domain!.

The only option I found was to go with option 1, and send a request like this:

{
  ...
  detailItems: [
    {
      type: "email",
      oldValue: "x@y.z",
      oldLabel: "Work",
      newValue: "a@b.c",
      newLabel: "Work",
    }
  ]
}

So I can do something like this:

card = agenda.getCardIdentifiedBy(the_identifier)

for(itemUpdate in jsonData.detailItems)
  itemClass = ValueClassMapper.from(itemUpdate.type) // "email" -> Email
  oldItem = new DetailItem(new itemClass(itemUpdate.oldValue), new Label(itemUpdate.oldLabel))
  newItem = new DetailItem(new itemClass(itemUpdate.newValue), new Label(itemUpdate.newLabel))
  card.updateItemWith(oldItem, newItem)

But, I don't know, maybe I have a wrong abstraction Can anyone help? Thanks in advance!

k-ter
  • 737
  • 1
  • 5
  • 15

1 Answers1

0

Part of the problem that you are running into here is that HTTP remote authoring semantics are at odds with the notion of an autonomous domain model.

HTTP PUT means "make your copy of this document look like my copy of this document". It is analogous to "save file", or "commit these rows to the database". The semantics of the request fit the model of an anemic data store.

On the other hand, our domain models are typically autonomous state machines, that expect to decide for themselves what state they should transition into based on new information. In other words, all domain model interactions roughly follow the following pattern:

domainModel.change(newInformation)

The model then changes its own data structures in response to this information (with the application code responsible for parsing the new information into a data structure that the domain model will understand).

This doesn't mean that you can't use HTTP and REST with domain models; but it does mean that the resource model looks very different from what you have described here.

The most straightforward approach would be to POST the new information to your agenda resource (autonomous domain entities really don't want you trying to directly modify their internals).

POST /agenda/12345
Content-Type: application/json

{
  ...
}

It would then be your application's responsibility to parse the body of the request, and then to pass that parsed representation along to your agenda domain entity, the domain entity would then decide how/if to integrate the new information with the information that it already knows about.

Another approach would be to treat the new information as a new document to be stored, where each new document would have its own URI:

PUT /messages/67890
Content-Type: application/json

{
  ...
}

Where the changes to /agenda/12345 are a side effect of storing the message -- see Webber 2011. Please be aware that this approach can make cache invalidation more challenging.

VoiceOfUnreason
  • 40,245
  • 4
  • 34
  • 73
  • I just watched the video. I understand everything is part of a process and I have to think about it. The thing is I want the user to update its contact card, and press save. If the form would trigger a save everytime a field is modified there would be no need for a save button and I could just do `PUT /cards//detailItem/`, but I am asking for ideas about handling a form which has a save button which let the user modifies many fields without seeing any "saving..." message until he clicks the button, and how to identify modified objects in my model – k-ter Dec 14 '20 at 15:50