Earlier I discussed how we identify bounded contexts. The example I used was implementing a system that only deals with the subdomain of desking the frontpage in a single bounded context. In this post I will discuss how we unite bounded contexts with HAL.
We attempt to split the application into bounded contexts to better handle complexity. Think of it as the single responsibility principle, but at a module or service level. This avoids complexity from one subdomain to leak into implementations of all services. As hard as finding and implementing bounded contexts are, the hardest part is tie every piece together in one end user experience.
For this example I will use five contexts from our domain
- Playback: (Everything related to presenting a video player, playing a video stream, reporting statistics)
- Personalization: Logged in users track progress of programs, favorite series etc.
- Frontpage: Editorial context, presentation of relevant programs, manually curated with images and plug-texts. This is images used in a context of a front-page, with titles and images that are only meant to be used in the context of a specific curated frontpage.
- Catalog: This is the structure of programs and series, with their common metadata such as titles, original titles and standard images.
- Recommendations: Given a program, I want recommendations for related content.
Below is a simplified and idealized version of the API. There are significant components intentionally left out, I have drawn the services that I am mentioning in this blogpost to keep the examples clear. Real life is always messier than a blogpost, and as much as I like to keep it honest and real it must be balanced against readability of the blog post. The maps that we use internally are messier and meant for an audience with deep knowledge of the domain and systems. In real life, we have more than 20+ clients.
The api-gateway is a reverse-proxy for the services. Reverse proxying traffic to new services is a fantastic enabler of refactoring using the strangler pattern. It creates an illusion of a single API externally, but lets each team deploy their own set of services behind the scenes. By having separate teams handle playback, desking of the frontpage and recommendations we try to align the problem space (subdomain) with the solution space (bounded context).
To connect the bounded contexts back together we need glue. There is of course the option to not help the clients at all, but let them keep track of IDs and resources in the different APIs and find a way to connect information. Hypermedia is another strategy to glue heterogeneous documents together. There are many hypermedia formats to choose from, such as HTML, Siren, JSON-LD, Collection+JSON. We could also have invented our own hypermedia format. We chose HAL due to its simplicity, while it is still powerful enough to cover our use cases. Most of our APIs are simple with little or no form data, so the possibility to describe forms, while useful, is not critical. The missing descriptions of forms is probably the main drawback of HAL for us. For userdata we need the possibility to add the userID to URLs, which is supported by HALs templated links. Details on HAL can be found in http://stateless.co/hal_specification.html.
The HAL links to point to resources in other bounded contexts, so that if the front-page wants to point the client to a representation in another context, it will have a HAL link to that resource. This means each representation in a bounded context must be reachable as a separate resource. In the simplest form it is simply a link from one resource to another with a named relation.
We create a unified user experience by composing the bounded contexts. The user wants access to all the information from all the contexts in one application, and sometimes multiple contexts at once. We can separate these types of composition in navigation between contexts, by UI modularization (several, but separated contexts handled by different modules on a single page) and by merging multiple contexts into composite contexts, either on the server or the client.
Composition using navigation
This is the simplest way to link across bounded contexts and gives a high level of decoupling. It requires the application to leave one context and go to another view with the new context.
One example of composing contexts with navigation is when linking the frontpage to that programs page with a video player. In the web client, it is the hyperlink from the frontpage on https://tv.nrk.no to https://tv.nrk.no/program/koid77000215/utan-mat-og-drikke
In the API, this is easily expressed in the front-page API using HAL links. The front-page consists of many programs, but not their complete representation. To render a page with the complete representation of that program, there is a self-relation that link to that specific program the client can simply follow.
This allows us to develop separate API applications for the program page (the self link), the playback (mediaElement) and for the frontpage.
In this way, we can link the frontpage context to the playback context using HAL links in the frontpage.
In some cases, the front-end can have separate modules that calls an API with very little interaction between the modules. Recommendation for a given program is such a case, we just need to know the link to the recommendation API. With that link a separate module can load that API resource and have all the information required for rendering the recommendations and reporting the correct statistics. Rendering recommendations looks trivial, but there is a significant amount of statistics involved in that component, so separating the module seems worthwhile. For more information on statistics for recommendations see the blog post https://nrkbeta.no/2017/05/18/hvor-godt-virker-algoritme-drevne-anbefalinger-i-nrk-tv/
All this module requires is: rel=recommendations, href=/tv/recommendations/koid77000215
The API is completely separate. It requires a single parameter input (the id of the current program), and have enough information to handle all logic related to the presentation of the module. Again, we use navigation to go from a recommendation-context to the program we were recommended.
Composition with composite contexts
One example of a composite context is playback with personalization. There is no obvious way to render the personalization parts as separate HTML fragments on the website. There might be logic that depends on the personalized data, but we might also want fallbacks in the case the personalization fails.
In the composite context case we can choose between:
- Composing on the server-side and having the server deal with loading playback data and personalization data in a separate API
- Having the UI do the composition itself
There are pros and cons of both strategies. For example, caching will be a lot simpler if the client can cache the entire static part of the response. HAL lets us enrich the playback context with personalized data, but allows for the client to support graceful degradation if the personalization systems should be slow or experience failures. However, all clients must implement identical logic that could have handled server-side.
The decomposition strategy has a related problem in organizing teams. Splitting the teams is not the hardest part, enabling them to effectively collaborate on coupled tasks is where we succeed or fail. We want the teams to be as autonomous as possible within a given context and build services that compose well. This must reflect on both the technical solutions we build and how we organize our work if we are to grow effectively. Therefore, our understanding of the subdomains, how they relate to our existing systems, the desired future system architecture, our organization, its resource and its strategy and priorities are all critical to the design of a well functioning API.
More info on the topic
Thanks to Asbjørn:
For a related presentation on the building composite UIs see Jimmy Bogards series on composite UIs: