10

My company developed a RESTful API that is used by a few internal applications. This API allows clients to charge one-time payments as well as recurring payments. Both of those things are made up of resources; a person needs a payment method to create a subscription that will create a transaction. The data is very relational, but one of our workflows is as follows.

Suppose a new person is looking to set up a recurring payment. Our workflow looks like this:

  • Create a person
  • Create a payment method linked to that person
  • Create a subscription linked to that payment method
  • Create a transaction that is linked to that subscription

Each of those operations is represented by an endpoint on one of our controllers that make up our application's REST API.

As the app has grown, many people are questioning why we are so steadfast in sticking to this modular API design. Any time a new client needs to integrate with this application to create subscriptions, they need to implement code that follows those four steps. Our answer has always been, "good software design, and REST, promote separation of concerns and keeping the construction of one type of resource separated from another allows us to keep things flexible and maintainable". The main counterpoint to this is that, like all software, we've had some bugs arise in between steps of this process and people argue that our problems would be solved (or less troublesome) if we had one endpoint that did it all.

The project is a few years old and has seen a good deal of production use. I'm very reluctant to believe that one "do-all" endpoint is the right thing to do but I feel like all I have to stand on in this debate is theory and principles. Some of our issues would likely have been solved by reducing the chatter between systems, but something seems inherently wrong with putting a bunch of distinct operations into one method and calling it a day.

Is there something I'm missing? Am I maybe biased because I helped design the app up to this point or are the other developers sacrificing maintenance and design in order to solve today's problems?

I'm hoping this question doesn't get closed as "primarily opinion based" because I'm looking for some answers grounded in real-world experience rather than theory/opinion (since I already believe that I, in theory, am right).

3
  • Separation of concerns is nice, but simplicity of use outweighs that, in most cases. Commented Sep 29, 2017 at 19:17
  • Are the four operations ever performed separately?
    – Eric Stein
    Commented Sep 29, 2017 at 19:30
  • There are cases where some operations are performed but not others. A one-time transaction, for example, stops at step 3. Commented Sep 30, 2017 at 0:25

2 Answers 2

8

Is there something I'm missing?

Yes. To start with, you are missing that your API isn't very good.

Let's look at your example in detail

Suppose a new person is looking to set up a recurring payment. Our workflow looks like this:

  • Create a person
  • Create a payment method linked to that person
  • Create a subscription linked to that payment method
  • Create a transaction that is linked to that subscription

Translated: the API consumer is trying to achieve one task. Doing so requires the orchestration of four different sub tasks. And your API insists that the consumer perform the work of achieving the orchestration.

What your consumers are asking for is to send one message to a concierge; and for the concierge to break that message into its component parts and manage the dispatching.

The Google home page used to be a perfect illustration of this; there was a link from that page into Google's search protocol, where the resources would provide you with links to navigate a catalog of results that matched your search. But there was also a second link, to Google's "I'm feeling lucky" protocol, which traded the fine grain control of the search protocol for a convenient, ad free experience.

There's absolutely no reason that your API resources shouldn't support multiple protocols. This might mean that you have many resources representing the same domain concept, where each resource is specialized to a particular protocol. That's normal for REST.

Jim Webber, in his talk REST: DDD In the Large

URIs do NOT map onto domain objects - that violates encapsulation. Work (ex: issuing commands to the domain model) is a side effect of managing resources. In other words, the resources are part of the anti-corruption layer. You should expect to have many many more resources in your integration domain than you do business objects in your business domain.

In other words, REST imposes no obstacles that would prevent you from improving the API experience for your customers.

What your API is offering instead is analogous to a Vogon bureaucracy, where one single recurring payment requires filling out four different forms and delivering them by hand to each of four different departments.

Your API consumers shouldn't be required to care that your protocol is implemented as four different domain objects with four different databases living in four different microservices - the point of the API is to insulate the consumer from the details so that you can more easily change the details in the future.

1
7

<insert rant about how the goal is to deliver value and being RESTful or following some OOAD dictum is not an end unto itself, also YAGNI/>

I think you may have a (common) misunderstanding of REST. I don't know if this is the case, but some of what you've said suggests that you might have a roughly one-to-one correspondence between REST resources and database tables. Even if you don't, the "dilemma" you articulate is a false one. Nothing about REST or any other design principle is stopping you from having something like /person/3/recurring_payment/1 It's completely okay and expected to have multiple resources that refer to the same entity. A POST to such a resource may do all the work of creating a payment method, subscription, and transaction. This might lead to a /person/3/recurring_payment/1 resource which may just be an alias for the created transaction or may provide a summary of all the relevant data.

Obviously no software design principle states that you can't provide convenience functions that combine functionality to capture common recurring patterns. You should still try to find coherent conceptual entities. In fact, it's not clear to me that your current API isn't essentially exposing implementation details.

Finally, I, personally, don't think REST is actually appropriate for internal APIs in many cases. The goals of REST are to make APIs that are long-lived, independently evolvable by separate administrative domains (e.g. separate companies), and scalable for slowly changing, coarse-grained documents2. While these properties are desirable, they are often not requirements of internal APIs. Furthermore, accomplishing them adds implementation and specification complexity as well as issues like increased latency. The result is that in practice many APIs are REST in name only. It's highly likely this applies to your API. For example, have you specified any media types? If not, you're probably not doing REST. Which is fine, but you should probably stop vaguely striving toward it. There are also, now, new API patterns that are clearly striking out in a different direction than REST, e.g. GraphQL. Time will tell how they do, but REST is not some perfect ideal of API design.

1 This suggestion is just to have something concrete to talk about. There's another common misconception about REST which is that it is somehow about organizing your URI paths in certain ways. In reality, REST doesn't care how your URIs paths are organized and ideally a client could (and should) be completely ignorant of it. This leads to things like HATEOAS.

2 "The REST interface is designed to be efficient for large-grain hypermedia data transfer, optimizing for the common case of the Web, but resulting in an interface that is not optimal for other forms of architectural interaction."

3
  • 1
    Your perspective is interesting because it largely overlaps with the angle that the other engineers were approaching this problem from. It's humbling to admit but it does objectively sound like I'm trying to adhere to a design guideline over constructing software for the job it is intended to do. Commented Sep 30, 2017 at 0:28
  • 1
    +1 for mentioning GraphQL Commented Sep 30, 2017 at 6:03
  • It is always possible to find a "design principle" to conflict with any design decision. Just look hard enough. Commented Oct 4, 2017 at 0:01

Not the answer you're looking for? Browse other questions tagged or ask your own question.