Impact-Driven Domain Decomposition: Navigating the Monolith to Microservices Journey
Adarsh Singh
Sr Engineering Manager @Atlassian | xFlipkart, xRedmart, xMercedes | SJCE
Summary
Migrating from a monolithic architecture to microservices is a formidable challenge and one that often underestimates the impact on both the business and its customers. Engineers frequently focus on the technical aspects of such transitions, overlooking the fact that these migrations span multiple quarters, involve multiple teams, and can disrupt business as usual. It's imperative to align the architectural journey with business outcomes, ensuring that the transition delivers incremental customer impact without undue interruption.
In this blog, we will share our approach and strategies that have proven successful in navigating this journey in the most impactful way. We'll also candidly discuss the mistakes we've made and the valuable lessons we've learned throughout our own journey. While getting Domain-Driven Design (DDD) right is crucial, getting the impact right is equally essential to justify the costs associated with these architectural changes.
Context and Background
In the dynamic realm of software development, Jira has traversed a remarkable evolution since its inception in 2002. Founded by the visionary duo, Mike and Scott, Jira commenced as a server product. However, in the pivotal year of 2016, this monolithic code base was forked, giving birth to what is now known as Jira Cloud - a sophisticated Software as a Service (SaaS) platform.
Picture Jira as a vast landscape of multitenant architecture. Each customer, or "tenant," is provisioned on a dedicated Shard—a self-contained Jira environment complete with compute nodes, database, cache, queue, and other resources tailored to their needs. The orchestration of this provisioning, which is nothing else than a science in itself, highlights the intricate nature and scalability factors that influence Jira's architecture.
The Jira journey is one of continuous modernisation. Decomposing a monolith is an engineering undertaking, driven not solely by technical considerations but also by a profound understanding of business and user impact. The challenges associated with monolith architecture are well-acknowledged. While the decomposition strategy primarily aims to isolate bottlenecks within each domain, it's important to note that the database is a significant bottleneck at scale, but not the sole concern. Each domain presents unique challenges and drivers, necessitating a tailored approach. This blog will now discuss the specific challenges within the Notifications domain. We aim to shed light on our approach to navigating this decomposition journey.
Notifications In Jira: A Fragmented Landscape
Understanding the current state of the Notification domain in Jira's monolithic architecture provides insight into the challenges that prompted the need for domain decomposition. In the existing setup, adding new notifications involves a decentralized and fragmented process.
The process of adding new notifications to Jira is complex. Traditionally, notifications are triggered from various sources. For instance, sending a notification when a user added a comment involved an intricate sequence:
One of the challenges stemmed from multiple different teams needing to generate notifications from and to different systems. Teams independently added diverse implementations for new notifications, leading to a lack of standardized APIs. For example, the mobile team introduced a new pipeline for push notifications, while the InApp notifications pipeline was added by a different team. This lack of cohesion resulted in inconsistent user experiences, making notifications irrelevant and noisy.
The decentralized approach outlined highlights the challenges inherent in the current state of notification within Jira's monolithic architecture. These challenges prompt the necessity of establishing Notifications as a separate domain. This will address the existing issues and, more importantly, enhance the overall notification experiences for our customers. By centralizing and refining the notification processes, we aim to deliver more cohesive, standardized, and user-friendly notification experiences.
Big Bang Approach: One Size Fits All Misconception
The shift from a monolithic architecture to the world of microservices is a subject that frequently sparks enthusiasm among engineers, myself included. Based on my experiences in startups, where the flexibility of microservices is often praised, I eagerly embraced the notion of separating the Notification domain from Jira's monolith.
With a strong desire to take advantage of the benefits offered by microservices, I developed a proposal that envisioned notifications as an independent microservice using a big bang approach. The proposal was well-grounded technically and supported by a comprehensive list of factors highlighting the potential advantages of this transition.
Technical Drivers:
However, despite the technical merits, the proposal encountered a substantial roadblock from our stakeholders—both from the product and business perspectives. The primary objection centered on the significant time and risk associated with this "big bang" extraction.
Valid Stakeholder Concerns:
Without strong drivers like scalability constraints a quick extraction approach for the Notification domain was not necessary, none of the resources were bottlenecked. The driving factors were centered on enhancing developer productivity, improving developer experience, and innovating with new functionalities.
Instead, we focused on an impact-driven domain decomposition that not only addresses technical complexities but also prioritizes incremental value delivery and sustained collaboration.
Impact-Driven Domain Decomposition: A Successful Pivot
We embarked on a new journey of Impact-Driven Domain Decomposition, driven not only by engineering insights but also by the collaborative forces of product and design vision.
A Collaborative Vision
The collaboration between engineering, product management (PMs), and design champions formed the bedrock of our renewed strategy.
Initiating this phase, our Product Managers (PMs) took the lead in articulating a high-level vision. This vision synthesised inputs from customer feedback, competitor analysis, and customer interviews. There are three key pillars.
Our design champions then stepped in to translate this vision into tangible features.
Jira architects created an overarching strategy for the future state of a decomposed Jira Cloud.
领英推荐
These collective inputs from product, design, and architecture provided a clear and cohesive direction, enabling us to formulate a strategy harmoniously synchronized with the goals of Product, Design, and Engineering.
Defining Scope & Principles
To achieve the vision three key categories of capabilities are needed:
We carefully defined the scope of this domain, clearly identifying what was included and what was not. Although our main objective was to establish a centralized platform, we took care to avoid hindering other products or creating potential conflicts.
We adhered to a set of guiding principles as a platform domain. This involved laying down foundational tenets to ensure that our efforts did not inadvertently obstruct other teams. We were intentional about not assuming a role merely as a proxy layer for teams seeking to send notifications. Instead, our primary objective was to provide capabilities that aligned with the high-level vision and goals outlined earlier.
Service topology
In designing the service topology for our decomposed Notification domain, we adhered to key principles aimed at maximizing efficiency and standardization. Embracing a platform-first approach, we leveraged existing solutions to avoid reinventing the wheel and steering clear of bespoke developments. The event platform has played a crucial role in efficiently consuming events from various sources, resulting in a cohesive and streamlined event-driven architecture. For permission management, we used the capabilities of the permission platform. Our commitment to standardized practices extended to the frontend, where Federated GraphQL became the backbone for all user experiences. Recognizing the diverse user base, we prioritized extensibility, providing developers with comprehensive APIs to tailor experiences to their needs.
Furthermore, by incorporating Rest APIs tailored for 2nd and 3rd-party integration, we fostered a collaborative ecosystem. We leveraged the platform's enterprise data store which provides functionality for enterprise use cases like Tenant lifecycle, Data residency, and BYOK. This approach to service topology laid the foundation for a robust and scalable Notification domain.
Extraction strategies: The implementation Phase
In the Implementation Phase of our decomposition journey, we embraced various extraction strategies to seamlessly transition from monolithic architecture and implement these customer-facing features. A highly recommended book for insights into these strategies is Monolith to Microservices by Sam Newman. Among the strategies we employed,
Aggregate exposing monolith: played a crucial role. Many core domains, including Fields Configuration, exposed domain aggregates from the monolith, enabling microservices to access entities managed by the monolith.
Branch by abstraction: this strategy was heavily leveraged, utilizing feature flags to coexist two implementations of the same functionality within the same codebase. This approach allowed for incremental rollout of new implementation until it could seamlessly replace the old one.
Change data capture: became a pivotal method for us, relying on events transmitted as CDC events from decomposed domains to trigger Notifications.
Monolith as data access layer: A strategic decision was made to treat the monolith as a data access layer during the initial stages of building these capabilities. This involved extracting the business logic behind APIs into microservices and accessing data managed by the monolith through APIs instead of directly interacting with the database. This approach expedited development, enabling us to deliver value to customers rapidly without concerns about data management.
Static reference data library: emerged as a significant move, establishing a common library that facilitated the sharing of static data such as enums and POJOs across both the monolith and microservices.
Strangler fig application: this approach was implemented to redirect existing calls in the monolith to the new microservices' implementation before migrating the entire feature or capability. These extraction strategies formed a crucial part of our journey, ensuring a smooth transition and effective implementation of the desired capabilities.
Charting the Course: Building the Roadmap
With a clear vision for the domain, we could focus on building a roadmap for its realization. To provide a structured approach, we established milestones for each capability, aligning them with both the current state and the desired future state. Collaborating closely with our design team, we developed mock-ups to visually articulate the features.
An integral aspect of our approach was to ensure tangible value delivery at each milestone. This commitment is aimed at consistently providing customers with incremental enhancements and features throughout the development journey. The involvement of Product Managers (PMs) became pivotal in this phase as they played a key role in prioritizing these milestones based on their anticipated impact on the customer. This roadmap embodied our commitment to delivering continuous value to our users at every step of our domain-building journey.
Cross-Pollination Opportunities
We reviewed the architecture Painted Picture and a high-level roadmap with dependent product and platform teams. The primary objective was to ensure a seamless journey without encountering blockers or red flags.
This collaborative exercise not only provided us with insights into the immediate future plans of these teams but also unearthed cross-pollination opportunities. By sharing our vision and roadmap, we discovered mutual interests, with several teams expressing keen interest in extending the capabilities we were building to enhance their own products. In certain instances, we uncovered that teams had already developed some of these capabilities, presenting valuable opportunities for learning and extension.
To ensure a continued exchange of updates and decisions we established a working group with representatives from each team. This group regularly synchronized to foster collaboration, share progress, and collectively navigate our shared objectives.
Collaboration Process: Fostering Collaboration
To aid collaboration we established a collaboration process geared toward facilitating contributions from various teams. We introduced a structured Contribution Model that not only aimed to enhance the Notification domain's capabilities within existing use cases but also prioritized the removal of potential blockers for other teams.
A significant improvement in this model was the introduction of shepherds to guide and support contributing teams during the development and shipping stages. By streamlining the process, we witnessed improved developer experiences, efficient decision-making, and alignment with our overarching goals.
Conclusion
In the ever-evolving landscape of software architecture, our journey from a monolithic to a monolith and microservice architecture has been both transformative and instructive. The overarching lesson is clear: the migration journey is not merely a technical endeavor but an alignment of architecture with business outcomes and user impact.
Our journey is a testament to the challenges of transitioning from monoliths to microservices. It underscores the importance of a collaborative, impact-driven approach that not only addresses technical complexities but also ensures incremental value delivery, sustained collaboration, and a cohesive ecosystem.