I am a big fan of both JSON API and Ember but I'm becoming increasingly unhappy with them when it comes to building complex applications. That has to do with my growing interest in Domain-Driven-Design (DDD). As I am learning more about DDD I realise how good it feels to not constantly think about your application in CRUD terms.
I am very interested how you work with Ember in complex domains and applications. Please share your experience.
The opposite of complex domains are domains where CRUD applications are sufficient to cover all use cases. CRUD is an acronym and stands for Create, Read, Update, and Delete. Nurtured by frameworks like Ruby on Rails and guided by JSON API and Ember Data we build most applications on the same foundation that is CRUD. This works remarkably well if you build something like a little note taking application. Someone using it can create new notes, update existing one, read a note and probably also delete it.
There are many applications where CRUD is not a good fit. Let's ignore the read and delete parts and focus on create and update since they are the parts where we usually first hit a mental roadblock when working in a complex domain.
Take for example an accounting application that deals with invoices. An invoice will never be "created" in the sense of how CRUD / JSON API / Ember Data make us think about it. It's far more likely and natural to create an invoice draft which you can modify until you decide to issue an invoice. In our business domain issuing an invoice involves multiple steps.
- Copy the invoice drafts attributes to the invoice.
- Associate the drafts items with the invoice.
- Set the due date of the invoice.
- Find and set the next sequential invoice number.
- Persist the invoice.
- Delete the draft.
- Create a due payment for the invoice.
- Generate an store the invoice PDF document.
How do we do all this in an Ember application? My naive approach is doing something like
POST /api/invoice-drafts/123/issue. Sadly doing this feels like going against the grain. The JSON API specification does not provide any guidance how to deal with Non-CRUD endpoints. There is also no support from Ember Data, you have to use an addon or implement your own solution. And on the server-side libraries like JSONAPI::Resources do not provide a clear way of supporting such actions.
Don't take this as a rant about the different parts of the ecosystem and their shortcomings. I'm just puzzled by the problem and would like to know how others are dealing with it because non of the possible approaches I can think of seem to be any good. How do you model something like issuing an invoice in your application?
Approaches I thought about
Using a custom and separate action/endpoint like
POST /api/invoice-drafts/123/issue. Personally this approach feels "right" to me. On the other hand there is close to zero official support and everything has to be hand-rolled. Which in turn means there are no conventions and no shared building blocks to move forward together.
Moving the business logic into the client, instead of having the API do all the work the client must do it itself. I see two issues with this approach the first one being code duplication. Even in a scenario where the client does all the work there will be business rules and validations on the server-side. Future clients will also have to re-implement the business logic on their end.
The second issue is the split up business logic in some cases. Take the invoice example, the client has no way of deciding what the next invoice number will be. So even though we are putting most business logic in the client the API will still have to do some things. We now have two very separate pieces of code that operate in the same problem space.
Introducing new resources to represent these custom actions. Instead of using custom Non-CRUD endpoints we could introduce a new resource type for our use case. A
POST /api/issue-invoices/endpoint would work nicely with Ember Data, JSON API and JSONAPI::Resources for example.
I don't like this approach, it feels like making an already defined domain even more complicated. My domain model already has an
Invoice#issueinterface, why would I want to make it more complicated to the outside. Naming things is hard, having to invent countless resource types for all of the domains possible use cases sounds scary to me.
Your feedback and ideas
As already said in the beginning, I have no idea what I am doing. So please send your tweets to @stravid, email me at email@example.com or drop by the edgy circle office in Salzburg.
I'm looking forward to your input. I strongly believe that together we can shed some light on this topic and make building Ember applications even better.