Search code examples
domain-driven-designendpoint

How to design API endpoint from a DDD perspective


While DDDs are domain-driven, RESTful API endpoint design rules are resource-driven, so they don't seem to fit in.

For example, the relationship between teacher and student resources is one-to-many. In the example, the aggregate root is teacher. (The :id part represents the ID of the resource.)

I think the endpoint below can be considered.

  • /teachers/:teacherId/studuents/:studentId
  • /students/:studentId

From a DDD perspective, I understand that data changes are made through an aggregate route.

So I think we need to use the first suggested /teachers/:teacherId/studuents/:studentId to modify it through the aggregate route, teacher.

However, some APIs do not require a teacher's ID. It is also cumbersome to modify student resources.

As I said before, the RESTful API endpoint design rule is designed around resources and the DDD is designed around domain, so the endpoint design may need to be changed. What should I do?


Solution

  • While DDDs are domain-driven, RESTful API endpoint design rules are resource-driven, so they don't seem to fit in.

    Yes, that's right - resource models and domain models are different things.

    Oversimplifying a little bit: a resource model is a collection of documents which act as a facade -- we make our service interface look like an HTTP compliant web server. People get copies of our information by asking for copies of our documents; people send information to us by proposing changes to our documents.

    Your domain model is, in effect, an implementation detail hidden behind the facade that is your resource model.

    In other words, you never GET an aggregate; what you GET is a "web page" whose internal data source is an aggregate. Similarly, you don't PUT/PATCH/POST aggregates, instead, you PUT/PATCH/POST web pages. The useful work in your domain is a side effect of the manipulation of the resource model.

    (Jim Webber's 2011 talk is an excellent presentation of these ideas.)

    And yes, if you receive a request to manipulate a resource that is backed by the domain model, then you are going to need something that allows you to take the request and from it compute the correct entry points into the domain model. That might mean parsing the request body, or it could be a simple as parsing the resource identifier of the request target.

    Again - that's an implementation detail. REST doesn't care whether you have aggregate root identifiers embedded in your URI, or if instead there are additional layers of indirection (remember: URL shorteners work on the web).

    (As the implementer of the request handlers, you might care quite a bit; which is fine - we have spare degrees of freedom in URI design, we can spend them to make things easier for people we care about.)

    Note that one of the important REST constraints is that representations are hypertext: clients don't type in URLs, but instead follow the links provided in the representations, or submit forms using the links in the form metadata. The URI don't need to be nice because the customer on the other end is interacting with the link, rather than the URI.


    So I think we need to use the first suggested /teachers/:teacherId/studuents/:studentId to modify it through the aggregate route, teacher.

    No, you don't need to do that. You can choose to do it - but it isn't required.

    If you are trying to keep the implementation of your handler simple, then you'll probably want to choose a URI that includes the identifier for the teacher and the student in it -- assuming that information isn't sensitive, that's fine.

    And when doing that you will absolutely want to choose identifier spellings that can be conveniently described by a URI template - which means that there will be a teacherId expression and a studentId expression within the template.

    But it is still the case that those expressions can be in either the path part or the query part, and they can be surrounded by whatever other structure makes sense.

    Your proposed spelling is an acceptable answer, but not the only one.

    Of course, if studentId alone is enough to identify the correct aggregate, then you can consider URI templates that don't include an expansion for teacherId.