Building a Smarter Account Dashboard with Domain-Driven Design

Molly Gehring
Policygenius
Published in
8 min readJan 28, 2022
Photo by Arie Wubben on Unsplash

Here at Policygenius, we have a simple mission of getting people the financial protection they need. However, we know that “financial protection” means something different to every person we interact with, and that to succeed in our mission, we need to create a platform that can accommodate all different types of financial protection needs. This makes our “simple” mission…not so simple. One of the tools we have leveraged to navigate the complexities of building our Account Dashboard platform is Domain-Driven Design (DDD).

How it started…

When Policygenius was in its infancy — as is often the case with young companies finding their perfect product-market fit — we were laser focused on building a solution that suited our needs at the time. And at that time, we were focused primarily on financial protection that included life insurance and disability insurance. Over time, we’ve expanded our product offerings to include other lines of insurance as well as other types of financial protection, like estate planning.

The account center we originally built for users to track the products they had with us excelled at modeling life and disability insurance, but made some assumptions about insurance policies and applications that didn’t scale effectively to our expanding domain. Over time, we started to see that we needed to create a platform to accommodate ALL of our users, regardless of their needs, and regardless of how many financial protection products we expanded into. We needed to create a consistent and maintainable front-end that spanned product lines. Because an application’s maintainability backbone is its data, we needed a back-end that aggregated data from multiple sources and standardized it for our front-end application. We certainly could have continued adding to, and modifying, our existing code base, but we opted to find a more sustainable solution.

How it’s going…

Our solution was to build out a new Account Dashboard following domain-driven design principles.

Domain

In the context of DDD, a “domain” refers to a particular subject area or sphere of knowledge. In our case, Policygenius’s overall domain is financial protection. For our team tasked with building the Account Dashboard, our sphere of knowledge, or subdomain within that overall domain, is user accounts. But how do we use our domain to drive design and how has it helped us solve the complexities presented in our original design?

Domain-driven design

The concept of DDD was first introduced in 2003 by Eric Evans and has withstood the test of time as a tool for modeling complex organizational problems. It’s an approach to developing software that is guided by (as the name suggests) the domain.

More specifically, DDD gives us tools to translate complex systems or business logic into software that can quickly adapt to changes in the system or logic being modeled. By placing an emphasis on communication between domain experts, stakeholders, and engineers, as well as across teams, DDD provides a framework for defining domain boundaries and creating a shared terminology, or “ubiquitous language,” so that the resulting software accurately models the real-world system and its logic.

Defining domain boundaries and creating a ubiquitous language is crucial in a business setting where concepts may mean different things in different contexts. Taking an example from the insurance domain, a user’s “address” may refer to their mailing address in the case of a life insurance application, but in the context of a homeowners insurance application, an “address” could refer to either a user’s mailing address or their insurable address — which could be two different places. By breaking out a complex system into smaller components, determining the boundaries of these sub-domains or “bounded contexts,” and getting everyone on the same page with what terminology to use, we can translate a complex system into logical software.

Why DDD?

While we software engineers may be experts at constructing complex lines of code like poetry, there is a very good chance that for most of the software we develop we will not actually be the domain expert. There will almost always be stakeholders or customers who are more knowledgeable about the domain than we are.

At Policygenius, I’d hazard a guess that the majority of our software engineers have purchased insurance or otherwise sought out financial protection measures, but it’s probably also safe to say that most of us did not start out here with much familiarity with the ins and outs of selling or underwriting insurance policies. And yet, we are tasked with writing software that requires in-depth knowledge of the insurance industry. It would therefore be a giant misstep for us to go about developing software without considering our larger domain and consulting with our domain experts.

Engineering teams must communicate with other teams and business stakeholders to ensure we are developing products that make sense. DDD provides a framework to facilitate this collaboration.

How we leveraged DDD to build out Policygenius’s Account Dashboard

DDD seemed like the right tool to help us model a very complex domain in a logical way that neither added more complexity nor abstracted away too much of our domain when building out our Account Dashboard.

Creating a bounded context

The first step in shifting to DDD was to determine exactly what part of our larger domain we were trying to model. We needed to create a space where users could at least:

  • Track insurance applications and policies as well as estate planning documents
  • Search for and learn about new financial protection products
  • Send messages to agents

We didn’t need to concern ourselves with anything that fell under the domain of other teams, such as where life insurance policy data comes from or where it’s stored.

Defining aggregate roots

Next, we needed to define our aggregate roots. The DDD concept of aggregate roots refers to clusters of various domain entities and value objects that can be treated as a unit.

Let’s consider insurance policies. If our only product offering was life insurance, naming would be easy. But we’ve already established we are operating in a complex ecosystem and we are dealing with products that, from a data perspective, all have different life cycles and attributes that are distributed across multiple services. To add to the complexity, terminology differs between different financial protection products. For example, the term “policy,” while used commonly in insurance, doesn’t really apply to estate planning products like a will or trust. We ultimately landed on the term ‘case’ to encapsulate any one of a user’s products and thus ‘cases’ became part of our ubiquitous language vocabulary.

Our concept of ‘cases’ became an ‘aggregate root’ that, following DDD convention, remains consistent in what it presents to the client (our front-end React app) even when the entities and value objects that it’s composed of change. For example, if a user changes the terms of their insurance policy, that data change should be of no concern to the front-end client. The client should only care about communicating with the aggregate, where any changes will be reflected.

So how then do we reflect changes in the aggregate root entity if, for example, an external service we rely on drastically changes its data model? Let’s take a look:

Mappers, Domain Entities, and Repositories

We opted to build our Account Dashboard back-end on Rails, but we needed to move away from traditional ActiveRecord models and create a layer of abstraction between the business logic that we were modeling and the persistence layer. We accomplish this using “mappers.” Mapper modules create a buffer zone so that if the data model for one of the services we use changes, it doesn’t have domino effects throughout our code. Imagine if one of the services we communicate with changed data model terminology for a user’s home “address” to “residence.” If we were directly using the data model returned to our application from the service, then we’d also need to change every instance of “address” in our application to “residence.” Mappers shield us from these ripple effects.

All data from external APIs is funneled through a mapper. These modules contain logic that allow us to take data in any form, shape it into a standardized data structure, and churn it out as a domain entity.

Let’s say that a user has a life insurance policy and some messages associated with that policy. We can use a mapper to bundle data for the policy and its associated messages into a single entity which will be stored with other entity instances in the aggregate root (cases) repository. Whenever we need to surface this data — like when a user logs into their dashboard and should see all their case data — all our front-end client has to do is ask the cases repository for a user’s case data and the user gets to see all their ‘cases’ (life insurance policies, home insurance policies, wills, etc.) in one place.

By using repositories, we achieve a separation of concerns where our domain logic is completely separate from the data persistence layer of our architecture. This has the added benefit of making testing easier, as we now have a standard interface for mocking and don’t need to be worried about having to rewrite tests every time incoming data changes.

Things to consider…

Domain-driven design has proven to be an effective approach for modeling complex business logic, but before trying to harness its power and set it loose in your code, there are a few things to consider. Just like you wouldn’t use a forklift to lift a feather, DDD can be overkill. If you’re working in a domain where complexity is not an issue (e.g., straight-forward logic and language), the legwork and planning that is required for DDD is going to be overkill.

It is also necessary to determine how knowledge is going to be held when considering employing DDD. Using a domain-driven approach, knowledge is likely to be pooled in different teams based on how the domain is split up. This can ultimately be a good thing, encouraging cross-team collaboration; however, it also requires engineers to have in-depth knowledge of domain boundaries and foresight to know when to communicate changes that may impact another team’s domain.

At Policygenius, we knew we couldn’t build our Account Dashboard in a vacuum, and we’ve welcomed the opportunity to collaborate with other teams while building it. We recognize that the code we write needs to make sense, both in logic and in language, to our broader domain. This requires the on-going dissemination of this knowledge to other teams via documentation and discussions. So yes, DDD does require a bit extra work at times, but we ultimately think it’s beneficial.

So if you’re dealing with complex business logic, don’t swerve out of control! Find a designated-driver to steer you in the right direction. Consider letting DDD be your DD!

We’re hiring in NYC, Durham, and remote. Check out our careers page to see open roles: https://www.policygenius.com/careers/

--

--