Practical DDD, Part 3 — architecture topic 2 — View service

Hila Fox
Machines talk, we tech.
6 min readJun 23, 2023

--

The previous architectural topic “Aggregate and Bounded Context Microservice“ referred to 3 practical patterns we use all the time in Augury. The aggregate microservice, the complex aggregate microservice, and the bounded context microservice. All three of these patterns relate to our need to define the practical components in our system for aggregates/bounded contexts which require state management. In this post, I want to refer to a new practical component which is called “view service”. Huge huge Kudos to Nadav Margalit who formalized this topic in Augury and I am using his content here.

BFF and Materialized views

Backend for frontend (BFF) is (as defined by Microsoft) a pattern to create separate backend services to be consumed by specific frontend applications or interfaces. This pattern is useful when you want to avoid customizing a single backend for multiple interfaces. This pattern was first described by Sam Newman.

Materialized views are a concept coming from the SQL world and refer to an object that contains a result of a query. For example the result of a join or aggregation query over a DB.

Combining the two together, we have generated a definition of view service.

View service — a practical architecture pattern

Though not from the DDD world, in our system we found ourselves in need of an architectural component that will allow us to aggregate/enrich data from different aggregates/bounded contexts for view purposes or manipulate data to suit a specific presentational use case.

Why was this pattern important for us to define?

  • We have multiple client types — in Augury we have both web and mobile clients. Implementing logic for aggregation, enrichment, or data manipulation for visualization would need to be implemented for multiple client types. This could mean different technologies, usually not the same people will be working on these features, and also usually very different development and deployment cycle lengths between the different clients.
  • DRY — Don’t! Repeat! Yourself! In general, an essential guideline in our world, even if we have only one client type but we need to perform the same data manipulations in several places, we would want this to be encapsulated in one place. When implementing DRY we carefully make sure that we are unifying code based on the same business behavior, and not based on superficial code similarity.
  • Performance — performing multiple API calls to collect data for manipulation would be more performant when all network calls are performed internally. On top of having better performance over synchronous communication.
    Another improvement is when we wish to save the aggregated data into a persistent state to avoid performing multiple calls. In this case, we need an architectural component to implement this behavior.
  • Is it the same as BFF? No, usually the BFF pattern is used when we need a different backed behavior for each type of client. But, currently, we don’t have use cases in our system that require different visualizations on the same data. Meaning that we can assume that data aggregations/manipulations/enrichments would be the same between mobile and the web. This is why there is no value for us to develop specific components/APIs per client type.

Granularity is the name of the game, what is the scope of a view service

Looking into view services opened up a very deep discussion for us regarding ownership in our UI. As we see ownership mapped to teams by their assigned bounded contexts we naturally included all the disciplinary layers embedded into a software team. Our backend domain is fairly distributed and defining architectural components that can be assigned to teams by bounded contexts was a simpler task. When thinking about UI we find ourselves in a new playground as distributed frontend or micro-frontends is a relatively new approach that still has a lot of technical limitations (at least compared to how progressed distributed backend is).

I don’t want to open up the topic of why micro-frontends here, there are plenty of reading materials available on the web. I want to focus on the question that impacts creating view services, the question is “What is the right granularity for an architectural component that a team can take ownership of. Meaning, what “chunk” of our UI does a UI area or potentially, in the future, a micro-frontend hold, and thus has its correlating view service.

We can go as low as UI components or as high as pages in our application and there are clear tradeoffs between the two spectrum ends -

  • UI component — the lowest granularity. It will enable being dynamic, encapsulated, and easy to identify in a bounded context but managing this low granularity technically would be very difficult. Take into account components’ chattiness, internal state dependencies, component deployment, and a lot of other technical requirements that will be X times harder as we have more micro-frontends and view services.
  • UI page — The highest granularity could be an entire page, this will enable us to have big enough components to manage. But, as we build our UX we have seen many cases where our pages include UI components that are related to more than one bounded context, since we want each team to maintain ownership of its bounded context this is going to be a problem.

The reason I am telling you all of this is that currently, understanding what is the right granularity for a UI architectural component is an open question that we have yet to answer but didn’t want to block us from formalizing guidelines for our backend architectural components. This is an ongoing topic that is being pushed by Serge Krul, our frontend guild manager, the frontend guild, and the architecture guild. So when you read the wording “architectural UI component” in the guidelines come to be aware that we know this is controversial on its own. We will fine-tune it in the future (if you are reading this and applied DDD conventions in your company, specifically on web applications feel free to share your thoughts on what granularity is best in your opinion).

Data types (definition) — can be aggregates or bounded contexts. In either case, an aggregation of data can be between multiple aggregates from the same or other bounded contexts

When -> Then to create a view microservice:

  • When we find ourselves aggregating 2 or more data types for a specific architectural UI component
  • When we find ourselves enriching from more than 2 data types for a specific architectural UI component
  • When we have performance issues related to data in a specific architectural UI component
  • When we want to improve our developers’ velocity and centralize all domain area API under 1 place

When -> Then to have persistence in a view microservice:

Due to the nature of our domain, being a B2B company which most of its scale is not by users entering our system (it’s in our data pipelines). Our need for persistence for performance optimization in regard to UI is not always needed. In most cases, read-time optimization is good enough. We can choose to discuss how fault tolerant each solution is but as our guiding principle will always be KISS we choose the simpler solution until product requirements arrive on the need to change it.

  • As a guiding principle, we should try to avoid as much as possible to persist view-service data and rely on the source aggregates data (reduce complexity and data management)
  • When we want to boost the performance of our application using read-optimized data
  • When the view service represents a new data state based on other aggregates domain events and we need the historical data

To sum up -

  • Avoid adding logic to your clients while encapsulating it in an architectural component that can be owned by a specific team, in our case by relation to a bounded context
  • Identifying the correct granularity for a view service is complex assuming we want view services to align with its UI architectural compatible components.
  • In order to avoid implementing aggregation/enrichment logic in clients use a view service.
  • In our specific case, we recommend using services as “logic holders’’ and not necessarily state. We will add state management to a view service only when it’s needed due to performance or business requirements.

We have several more architectural topics —

  • Cross-cutting services — integrations, infrastructure, and tools in our system.
  • Communication channels — how services communicate.
  • Control flows — orchestration, choreography, and more fun stuff
  • working on more :)

--

--

Hila Fox
Machines talk, we tech.

Software Architect @ Augury. Experienced with high scale distributed systems and domain driven design.