nrk.no

On architecture, third post: Composing bounded contexts

Kategorier: Dev & English-articles

Photo by Ricardo Gomez Angel
Photo by Ricardo Gomez Angel

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.

Multiple APIs behind a reverse proxy (gateway)
Multiple APIs behind a reverse proxy (gateway)

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.

 

HAL in the response describes relations across bounded contexts as links.
HAL in the response describes relations across bounded contexts as links.

 

Composition-strategies.

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

Programpage
Navigation from frontpage  to program page (shown)

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.

 

Part of API response from the front-page with the self-link to the program shown.
Part of API response from the front-page with the self-link to the program shown.

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.

 

Front-end modularization

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 modules requires is: rel=recommendations, href=/tv/recommendations/koid77000215

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.

Choosing strategy

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:

and Einar  (https://vimeo.com/113698604)  for valuable feedback on this post.

For a related presentation on the building composite UIs see Jimmy Bogards series on composite UIs:
https://jimmybogard.com/composite-uis-for-microservices-a-primer/

3 kommentarer

  1. Knut-Olav Hoven

    Very nice and informative!

    JSON-LD is actually not a hypermedia format. It can link to other resources, but if resolved to a document, the client expects to see another JSON-LD document.
    It’s flexible though, and can be extended with for example Hydra.

    And actually, by defining link relations with special meaning (and documentation if one is kind enough), clients can know by that definition what contracts and possibilities exists for that linked resource.

    Svar på denne kommentaren

  2. On architecture, fourth post: A change of perspective

    […] I discussed in my previous post, our philosophy for new API endpoints is to not have a central concept of a program. A program […]

    Svar på denne kommentaren

Legg igjen en kommentar til Bjørn Einar Bjartnes Avbryt svar

Din e-postadresse vil ikke bli publisert. Obligatoriske felt er merket med *. Les vår personvernserklæring for informasjon om hvilke data vi lagrer om deg som kommenterer.