Tips For Right-Sizing Microservices Kristopher Sandoval July 13, 2021 Right-sizing your API is one of the most important considerations you can engage in at a product level. Ensuring that your API does what it should do seems like an obvious process, but clarifying the exact parameters and setting those expectations for your users can be quite complicated. Today, we’re going to dig into what right-sizing means for the average API developer. First, we’ll discuss microservices and monoliths, as well as some misconceptions regarding those two paradigms. Then, we’ll look at some easy steps any API developer can walk through to right-size their service, considering specific business logic to drive the right-sizing processes. Clarifications and Terms Before exploring this topic, we should define some terms used often in this context. The core concept of “right-sizing” suggests there are “best practices,” and as such, adopting a common language and understanding will be critical to effectively right-sizing. First, what does right-sizing even mean? The concept of right-sizing hinges upon the idea that an API should be large enough to do what it was meant to do, but not so large that it becomes unwieldy or ineffective. Of course, this can be subjective, and as such, can be a somewhat nebulous topic. There is no one perfect right size because the right size for an API will be highly dependent on the API’s purpose. A good way of thinking about this is to imagine a statement such as “I want to buy a car.” What is the perfect car in this situation? For a city driver, the needs will be vastly different from, say, a rural driver who lives uphill on a steep grade. For this reason, before you even begin to try attaching a “right size” estimate on an API, you should figure out what the scope of your service actually is. Next, we should define two prevalent terms that pop up in these types of conversations. First, let’s discuss what a “monolith” is. You will often hear right-sizing in the context of “not being a monolith,” but the reality is that the idea of a monolith is not in and of itself bad — it’s just a very particular approach for a very particular set of circumstances. The monolith is the concept of a singular “mega API,” wherein everything is handled by a single service or stack of services running on large and complex code. One of the drawbacks of such a paradigm — and this is the main reason people avoid monoliths in the modern space — is that the maintenance of such a system results in service creep that makes the API ever more complex with additional functions, making it harder to implement changes or adjust to new business environments. Microservices are often positioned as an antithesis to the monolithic frame of mind. If the complex interactions of a codebase create a stack of services, why not simply separate out those services? Breaking the monolith into multiple smaller parts means that each individual API can carry out a specific discrete function (or set of functions) more effectively and in isolation, working with other discrete units when the need arises. This independent functionality and extensibility grants microservices the ability to pivot and be agile, but it comes with its own costs (which we will discuss shortly). Steps to Right-Size Now that we have discussed right-sizing in general terms, let’s look at how we handle the actual size of what is built to see whether or not the API is right-sized. There are a million techniques for doing this that have been espoused by many developers, thought leaders, and experts, but they all generally break down into a few specific categories. 1. Audit Your API Purpose Understanding what the end goal for your API is will help you determine what approach to take. For example, smaller discrete functions operating independently rather than as part of a suite lends themselves to microservices. In contrast, something that uses everything and depends on an entire flow utilizing a stack of services is more appropriate for a monolith. Right-sizing isn’t just right-sizing for resource use or for what you can support at this point in time; it’s also right-sizing for the fundamental purpose and form of the app, now and in the future. There’s also a more general question of how much is too much. When building out an API, it can be tempting to simply toss in every possible function that you could imagine would be needed in the typical API workflow. The actual use case APIs sit in, however, is as a set of collective services — accordingly, look to open-source and default methodologies as much as possible. 2. Audit Your Resources Do you have the ability to have multiple teams, focuses, etc.? If so, a microservice may be appropriate. However, developing a microservice can be resource-cheap — creating a collection of microservices, however, can be expensive and time-consuming. In such a case where there are not enough resources for such a development paradigm reality, a monolith may indeed be appropriate, especially if it’s paired with a reduction in scope. Ultimately, your resources will define your scope and ability to execute and will ultimately be one of the more important facets of consideration for right-sizing. 3. Audit Your Plans Much of the resistance to the monolithic paradigm has come not from the proper application of the paradigm, but from the difficulties that come with converting something that should have been a collection of microservices from the beginning to microservices well into the lifespan of the service. Properly auditing not only what your API is, but what it might be in the future, can help you choose the right paradigm early on and then adjust your scope accordingly. 4. Adopt a Scope Document One of the best things you can adopt early in the development of an API is a scope document. Each API is essentially a project, and in the project management world, a scope document sets the limits of that project. In addition to ensuring that development does not result in creeping coverage and implementation, project scope documents can set expectations for early development milestones, deadlines for functions or features, expected resource costs, and even discrete steps that must be taken to build and improve the service as a whole. Once a service scope is established, it is also helpful to set and manage expectations. When communicating the form and function of an API, don’t promise the moon — promise what it will actually perform with the available resources. Too often, APIs go out of scope because they’re trying to be everything for every user. If you set your expectations early on and manage the developer users’ expectations, this particular pressure point can be prevented. 5. Practice Event Storming One method that has been particularly helpful for planning technical implementations is the idea of event storming. Born out of domain-driven design, event storming is a methodology where services are designed by domains of function and business needs. It is a global boundary-setting method. In essence, all of the project stakeholders will meet and align behind an understanding of the business need, the domain implementation, and sometimes, the technical solution. In this way, API developers can treat their API as a project with a defined, narrow band of functions and expectations that meets all primary needs of the core stakeholder group. 6. Establish a Feedback Look With Developer Users Finally, establishing a consistent line of communication between user and developer is a vital tool in preventing feature creep. API developers should strive to strike a balance between the things that need to be built and the features that would be nice to build. This balancing point requires a strong line of communication. Users can help feed into the system what they’d like to see, and developers can help communicate deadlines, resource constraints, and long-term plans effectively. This constant communication will help set your organization’s tone and limit what can actually be done within the confines of the user request and service fulfillment. Is the Monolithic Paradigm Bad? As part of this question of right-sizing, there’s an underlying discussion concerning whether or not the monolith, as a concept, is a bad one. Too often, the advice is to avoid monoliths because they are monoliths; the reality, however, is that painting monoliths as universally bad skips over a lot of context and caveats. Yes, indeed, an API should not be a monolith unless it is absolutely required. Further, an API should only be as monolithic as necessary to function for that core expectation. Monoliths are not intrinsically bad — more often than that, it’s the improper adoption of the monolith paradigm. The idea that all things should be microservices misses the very good use cases for monolithic API development, especially when that API should by design be a stack of non-isolated functions. Monolithic models are appropriate when a single team can conceivably own the entirety of the API stack, as long as the size of that monolithic API is not itself a blocker. When a single team owns a single API, and that API is a stacked set of dependent functions, a monolith may be the most appropriate choice. This model is also appropriate when a certain level of security or internality is required; microservices create more opportunity for exposure, data interception, etc., so in some minimal cases, a monolith would make sense as a single secure nexus. It should be noted that the main concern around monoliths, that they are inflexible and prevent adaptation, is only a concern if such pivoting is a possibility. For example, if building a single-purpose API that only does one thing in a secure environment is the case in question, a monolith may, in fact, benefit from this more restricted nature. When to Leverage Microservices When, then, should microservices be adopted? Microservices as a design pattern reflect the idea of breaking services into multiple smaller parts, all of which can carry out their core function independently while still allowing for interconnectedness and transit with other APIs for higher functions. The main benefit of such an approach is that the microservice is scalable and can independently expand or contract aside from the greater collection of services, which delivers greater agility. The answer of when to leverage microservices then comes with that very nature. As each microservice is supposed to be independent, an important qualification for adoption is that each function should exist independently and function as a node in the greater network. Segmentation of function and form must be leverageable; otherwise, you’re breaking up something that is highly dependent on its internal functions into a monolith in pieces. There are drawbacks to microservices that should be mentioned. Since you’re creating lots of little APIs, you need both more resources to manage those discrete projects and a longer total development cycle. To be clear, it is true that microservices can enable faster development and quicker delivery, but this gain is typically found when you are making one microservice or a small number in total. Moving a monolith over to a collection of potentially tens or hundreds of microservices can be more time-expensive in some situations. If this is not something you can support, then your best option for right-sizing may be to adopt a monolithic model, but keep the scope very small. Resource demand can also be a concern — logging, caching, performance, etc. can become problematic at microservice scale when an API is just being created, and as such, if you are concerned about having multiple different avenues for logging, reporting, etc., a microservice may not be the best choice right off the bat. Conclusions The advice in this piece should be used not as a hard and fast set of rules, but rather as a framework that may help guide you in the right direction. Ultimately, right-sizing microservices will depend highly on what you want the microservice to do, look like, and support. For example, a monolithic service with a wide-reaching feature set will be appropriate for specific business cases where microservices providing a minimal domain of functions would not be. What do you think about these tips? Are there any more tips you think are worthwhile to help right-size APIs? Let us know below!