This post is a continuation of our series about paying down technical debt and re-architecting our platform. You can read the introductory post here: Paying Technical Debt to Focus on the Future.
In today’s competitive marketplace, businesses must improve the quality and scale of their products to keep pace with ever-evolving consumer needs, like finding a home. From a technological standpoint, microservice architecture is a way for large organizations to align infrastructure with business objectives while maximizing agility and flexibility. Here we describe how Trulia developed a strategy to coordinate our work to relieve the burdens of a monolithic system by transitioning to microservices.
From Monolith to Microservices
The term monolithic architecture describes the situation where a large amount of functionality lives in a single service which is tested, deployed, and scaled as a single unit. With increasing velocity, overall defect rate skyrockets as the complexity of the overall system increases. At Trulia, we sought to break down slow-moving, monolithic systems and replace them with small, independent services that are decoupled and self-contained, and focus on a specific function or capability.
Typically, when monolithic architectures are exposed to growing load, it is difficult to locate which components of the system are actually affected. Since the system runs within a single process it requires the whole monolith to scale, e.g. by replication or horizontal scaling. One such example of how we scale legacy applications horizontally at Trulia is shown below.
When we decompose the monolith, the microservices are deployed independently of each other and run as independent processes, hence, they can be monitored and scaled independently.
The separation of services, or vertical scaling, allows us to tune capacity for the specific services where it’s required, instead of adding capacity to run more instances of every service in the monolith. We had some initial success in deploying microservices, but we encountered some predictable problems, like the challenge of managing the larger number of deployments and increasing complexity in dependencies.
Instead of dealing with one application running on a single server or across many servers, we break it up into elements written in different programming languages, running on different virtualization containers, and deployed across different cloud and on-site locations. When there is an increase in demand for the application, we must identify individual elements that need to scale to address the spike in demand.
Trulia’s Microservices Strategy
Three years ago, when I joined Trulia, we were seeking a direction for building a simpler solution that is easy to develop, test, deploy and scale. At the time, with the evolving needs of the organization, teams experienced difficulties with the growing monolith, like loss of agility over time, features requiring longer development time and a fragile codebase. The lack of clear software architecture and boundaries was possibly due to limited time and heavy dependence on a system that inhibits high cohesion and encourages tight coupling.
As result, the focus shifted to decomposing the applications in order to abate the low cohesion that results in fragile codebases and slower development. In order to address the increased complexity, we turned to Chris Richardson’s pattern language, available in Microservices Patterns, which builds on Domain Driven Design (DDD). The key idea behind DDD is to organize the code so it is aligned with business objectives and utilizes the same terminology
To articulate our strategy, we formed a small workgroup, comprised of engineers representing all of Trulia’s development teams and started with two days of training with Chris Richardson. One of the key points in the training is that there must be a structure for decomposing the services. After the training, the group began to work on a domain model using the technique known as “decompose by subdomain.” The domain model frames a set of services owned by autonomous teams that adhere to the Single Responsibility Principle which states “gather together those things that change for the same reason, and separate those things that change for different reasons.”
Members also took on specific patterns from the general pattern language to find good examples in our existing implementations and specialize them into our own pattern language wiki that serves as a common baseline of practices. We also created a ‘microservices guide’ to describe our domain model and identify the patterns that we require all microservices to follow, like log aggregation and request tracing. Our primary focus is to model services around a business domain. Each of these services owns and maintains its data store with no sharing of datastores across services. Independent functions use asynchronous communications and are self-contained to assure isolation of failure. When building or updating a service, most of the patterns are broadly applicable and should be considered.
To be fully compliant in the Trulia system of microservices, each service must adopt patterns that apply to its role in the system for functions especially tracing, logging, service discovery, and registration. With the pattern language that we created, we have a method to spread the approach and coordinate the adoption. We have implemented several services that are fully compliant with applicable patterns and we are incrementally migrating the remaining services to implement the patterns that are relevant to them. As an example, in Trulia’s real estate listing acquisition pipeline, the search services employ the Command Query Responsibility Segregation (CQRS) pattern.
While cost is always a consideration, we focus on the end goal of keeping our stack running efficiently and reducing complexity with optimal practices. That means expanding patterns with libraries and microservices chassis to assure that developers have standard ways of incorporating patterns into microservices. We are looking to extend those patterns to encompass the architecture for the Data Science and Analytics teams. For example, we have successfully enabled the choreography approach of applying the Saga Pattern in order to maintain data consistency across multiple subsystems.
Trulia is continuously evolving and striving for simplicity through reuse. Today we have three key functions running on our new microservices architecture, a fourth island in production, and are continuously searching for the best ways to migrate other tightly coupled services to our new architecture.
Stay tuned for more post about our transition to microservices, and click here for a practical overview of our first product built and launched on our new stack.