Working inside an enterprise is constantly a challenge to balance competing demands and chart the best course forward. Unfortunately, typical corporate IT culture is one where everything is ruled by project delivery metrics: on time and on budget. Based on behaviors I’ve observed, this results in two common things:
- Efforts to minimize the teams involved. More teams means more coordination which creates risk to the schedule. If you think about this, concepts like micro services and two-pizza teams all really have to do with the fact that it’s very hard to control the output of a team of 20 to 40 people.
- Where multiple teams must be involved, each team will argue to minimize the work that they need to do (they don’t want to be perceived as the ones who blew the budget). I see it more frequently from the consuming side (e.g. user interface team saying, “Can’t you aggregate that data for me so I can just plop it in this table?”), but it can come from either side. Sometimes these questions will be masqueraded as “performance concerns”, even though in reality, we may just be shifting work (i.e. doing the exact same orchestrations, just out of different components that are owned by different teams).
While I could write a whole blog (and probably have) about the impact of focusing on project metrics, that’s not the point of this post. The fact is, these pressures exist and will continue to exist, so we need to have a plan on how to deal with them.
On the effort to minimize the teams involved, the first thing you must realize is that the decisions you make are about the next project, not this project. So, if you are an API developer, what design decisions can you make on this project such that when the next project comes along, they can simply use your API rather than having you involved in their project to modify the API?
To solve this problem, you need an API that is focused on breadth of use. To understand the breadth of use, don’t look at the demand side (what your consumers ask for), look at the supply side (what you have to offer). You can try to predict what consumers might need, but odds are you’ll be wrong. If, instead, you look at your backing information stores, and come up with an API that makes all of that information available, there’s no use case that won’t be covered. If you do this, you now empower consumers to get at what they need without your involvement (at least from a development perspective).
But what about performance?
This is where the competing demands can rear their ugly head, and it’s definitely more common to internal efforts? Why? Because most of the people in IT have some knowledge of what’s going on behind the scenes. They can ask the question, “why can’t I access your database directly?” because they know that database exists. Ask them this question: “If this were an Internet-based API, would you be asking the API provider to let you run SQL against their backing database?” Of course not. Having some insider knowledge can be a dangerous thing that can get us into traps where teams debate best performance versus good-enough performance. Will removing service layers and allowing direct database access yield faster applications? Probably, but at what cost and risk? Are you willing to put the integrity of your data on the line to do this? Are you willing to create risk that some business change could result in a change to all of those systems now hitting that database? There are more goals than just performance, and we need an approach that strikes the right balance on all goals, not just over-achieving on performance. Remember, Internet-based APIs continue to power many innovative web applications with perfectly acceptable performance.
The answer to the breadth-of-use versus performance debate, in my opinion, is to have a two-layer API strategy. This is consistent with what James Higginbotham wrote about in this blog on the Front-End / Back-End API Design conflict.
Layer one is an API focused on breadth of use, where design is all about exposing as much information as possible in a high performing way. Keep in mind that you should still need to define appropriate API boundaries, and the view of performance here is within your boundary of ownership. Know what things influence the performance of your API, and give your consumers control over those decisions. So, while your API may be capable of exposing all information from within your domain, that shouldn’t be the default behavior. If there’s a collection of information that is more expensive to retrieve, give the consumers the power to decide whether they want to incur that expense or not through a parameter in your API. I like to call this layer the Business API, but you may prefer Internal API.
Layer two is an API focused on context-specific use. I like to call this the Interaction API. While the most common use cases will be for a front-end, that’s not the only case where context comes into play. Merely exposing something externally versus internally is a context change. While you may want an API for all of your information internally, you’re not going to want to expose all of that information externally. Additionally, aggregations across boundaries that can be done efficiently inside the firewall might not be so efficient from outside the firewall. The Interaction API is the place where optimizations geared toward more specific contexts are done, if they are needed. In more cases than not, this layer probably is needed, but don’t view it as mandatory. If what you have from your Business/Internal API is perfectly suitable for direct access by the end consumer, just use it.
The other facet of this layer is that it puts ownership of logic in the right spot. If it’s a particular front end that needs an API highly tailored toward their UI design that can be called out of the browser, let that team build it! The particular combination of data is probably unique to their situation, so let them build it on top of the Business API that has been provided to them.
This two-layer approach to your API can hopefully help you avoid the debate around competing demands and instead get a solution that provides proper balance across both objectives.