When our engineering team first began conceptualizing Aha! Develop, we were faced with a monumental question. How should we implement the architecture of adding a brand new product? We could start fresh with a brand new codebase, utilizing the latest technologies and frameworks. Or we could build on top of our existing monolithic codebase that powers Aha! Roadmaps and Aha! Ideas. We examined the pros and cons of greenfield, microservices, or continuing to use our existing monolith — all in pursuit of the most lovable solution for our users.
Option 1: Greenfield
Starting with a brand new codebase is really appealing. We could keep it simple without any extra baggage like complex dependencies, legacy code, technical debt, or old build pipelines. Using the latest versions of all of our favorite frameworks like Rails 7 and React 18 would mean not having to worry about testing for regressions. And we could start fresh with a new design system or CSS framework.
Even if the new greenfield product does not have a lot of overlap with previous products, you might find yourself rewriting common systems like authentication, permissions, notifications, or other things you’ve implemented before. Most applications share a lot of this core functionality regardless of the product itself.
Option 2: Microservices
Microservices have been popular in the past several years. Building Aha! Develop on top of microservices would make our application more modular and scalable. We could transition core functionality of authentication, permissions, notifications, and sharing/publishing to their own microservices. These would lead to smaller pieces of isolated code that are only responsible for a concise set of tasks. They can be scaled independently in order to maintain service level objectives (SLOs) or other key metrics. Then Aha! Develop could reuse these core services with a UI built separately from the other products in our suite. All of our core business logic would be nicely encapsulated, tested, and deployed separately. Any number of interfaces or APIs could be built on top of that foundation.
That would all come with some fairly significant tradeoffs. As the dependency hierarchy between each microservice or application grows, changes require complex coordination between various teams to prevent regressions. When new features are needed, meetings and thorough documentation ensure the necessary APIs behave as expected to support the desired functionality. Teams must coordinate back and forth over weeks or months to manage the testing, development, and deployment of the new features. As the product requirements evolve, additional meetings and work are needed to support the new behaviors.
The benefits of having simpler code in microservices get eroded by increased process and operational complexities. Sometimes those tradeoffs are worth the effort, though.
Option 3: Monolith
Our third option was to continue building on top of our existing monolithic application. We had already added Aha! Ideas to our suite of products in late 2020. This gave us the confidence that we could build another new product using a similar approach, even though it was targeting an entirely new demographic — developers. This approach also allows for better integration and consistency for customers using multiple Aha! products together.
A monolithic application can also quickly run into performance limits, often from an underlying service. For example, due to the large volume of job queuing, we have run into CPU and memory bottlenecks with our Redis-backed Resque service. We were able to make improvements to our job queuing by implementing a Kafka-based queuing system. This improved the performance and reduced the latency of our jobs. The jobs are still executed as part of our monolithic application — meaning the operational costs are fairly isolated.
Extending the monolith
Extending the monolith ended up being the clear winner for Aha! Develop. We already had a solid foundation of core functionality. Adding new features or APIs would not be any more difficult than if we had chosen greenfield or microservices.
By embracing our monolithic codebase, we were able to launch Aha! Develop on top of the foundation we had already built. For example, instead of reimplementing authentication and permissions from scratch, we focused on building compelling new features like extensions, the workflow board, and our new GraphQL API.
Security and permissions
Aha! takes security and permissions very seriously. Building the same level of security and compliance into a brand new codebase would take a significant amount of effort. It would also increase the surface area for attacks by having more infrastructure that needs monitoring and safeguarding, as well as additional codebases that must have their dependencies reviewed, approved, and updated to protect us against security threats.
To support Aha! Develop, we built a new GraphQL API on top of our existing, rich Aha! data schema. This API supports many features in Aha! Develop, such as the workflow board, backlog management, and sprint planning views. Extension authors can also interact with the data or create custom extension fields to customize the tool to suit their needs.
Since we built this into our monolith, the API takes full advantage of our existing data model and Aha! Ideas and Roadmaps take advantage of the new GraphQL API. We are also able to implement performance improvements that benefit all of our customers, such as our solution to automatically avoid N+1 queries in GraphQL.
One of the major benefits of using a monolithic codebase is the ability to reuse code. Our Rails monolith uses many common patterns for writing reusable and modular code. We've used patterns like concerns, service objects, and decorators to help us avoid repetitive code, simplify testing, and provide consistent behavior across the application.
We reused code to add components like comments, to-dos, record links, and audit history to Aha! Develop with very little effort. For example, we added a new model for Aha! Develop — the iteration (or "sprint") model. We use Rails concerns so our models are composable. Here are a few of the concerns we included in the iteration model:
include HasDescription include HasWatchlist include Terminology include ActsAsTabbedRecord include ActsAsCommentable
With these few lines of code, our iteration models included many advanced behaviors we had already implemented elsewhere in Aha!
Not only is reusing the models helpful, but we could also reuse our existing views. This gives us access to our drawer and details design that lets Aha! Develop manage custom fields and layouts with a simple and intuitive interface. We have now added a completely new model to our application that looks and behaves just like the others.
Collaborative text editor
Our rich collaborative text editor is one of the many compelling features in the Aha! product suite. Including it as part of Aha! Develop was a critical feature. We even added syntax highlighting to the editor — an improvement built for Aha! Develop — but now all Aha! customers can take advantage of this great feature.
Adding a new product to Aha!
Since all of this core functionality was already available to us, adding a new product to the Aha! suite was relatively simple. The only difficult part was focusing on Aha! Develop’s differentiating features.
Develop "teams" were constructed as a natural extension to our existing workspace hierarchy. This allows team configuration and settings to behave similarly to workspaces in Aha! Roadmaps. Each team can configure their own statuses, terminology, and workflows. This was yet another way to build on top of existing patterns used in Aha! Roadmaps without writing a lot of new code.
Billing and permissions
Adding a new product to our billing system was not without its challenges. The system was originally built to support only a single product with a few different pricing tiers. Our new billing system would need to support several different products, with a number of pricing tiers each. We introduced additional subscription plans and can now support accounts that use multiple products. Customers can have quite a few different billing configurations but there are some we need to prevent.
To implement this, we introduced a new "flavor" model that is used to determine the billing plan, pricing tier, and number of seats for the various products in our suite. Previously this information was stored at the account level. This flavor model also dictates available functionality from within the application. We can now use a single model to manage the billing and available functionality for accounts.
Launching Aha! Develop
In the end, we didn't need to reinvent any wheels. We didn't reimplement permissions, authentication, comments, custom fields, or layouts. All of these features have evolved and matured over many years, representing countless iterations and improvements to the Aha! product suite. It would be wasteful to throw out all of that effort. Embracing the monolith allowed us to build and launch Aha! Develop with a rich and mature interface — while saving our developers months of effort and providing a Complete Product Experience for our users.
Sign up for a free trial of Aha! Develop
Aha! Develop is a fully extendable agile development tool. Prioritize the backlog, estimate work, and plan sprints. If you are interested in an integrated product development approach, use Aha! Roadmaps and Aha! Develop together. Sign up for a free 30-day trial or join a live demo to see why more than 5,000 companies trust our software to build lovable products and be happy doing it.
Top comments (1)
I'm sorry in advance about Graphql