loading...
Cover image for Message-based APIs

Message-based APIs

kspeakman profile image Kasey Speakman Updated on ・3 min read

REST APIs seem to have taken over dev mindshare. I believe part of that is how well they map to already-understood concepts like databases. Databases tables are nouns, REST resources are nouns. Databases have 4 manipulation operations INSERT, SELECT, UPDATE, DELETE; REST has 4 HTTP verbs POST, GET, PUT, DELETE. Also known as CRUD.

While REST APIs are a great alternative to direct database access for untrusted clients, they are often an uncomfortable fit for application APIs. Trying to use them in this way ends up causing the same issues as trying to use a database as your integration point. See Integration Database.

By application APIs, I mean APIs which are not just CRUD wrappers over a database. There is some business logic involved that goes beyond checking for blank strings.

Application APIs often need to handle use cases or user stories which cross multiple "resources". Much like the Integration Database, when you model this in REST, the client has to know all the resources to touch plus handle failures along the way. In other words the client becomes responsible for use case handling. In practice, I often see "REST" operations on the server affecting other resources and performing other side effects because it is too inconvenient to let the client manage this. The problem is that now the operation has non-obvious side effects. Like triggering an email. It makes what is supposed to be a general operation not really reusable for other purposes.

Ok, enough background.

To avoid these issues, for a little while now I have been developing message-based APIs. In this kind of API, the client simply constructs a message (commonly a class that gets serialized to a wire format like JSON), and sends it to an API endpoint. It is similar to RPC except that the arguments are packaged into a first-class concept - a message - which represents a use case. An HTTP message API endpoint might look like this.

/api/{message_type}

The {message_type} is often something like a class name. Some possible examples of calls to this API.

POST /api/Register
POST /api/BulkRegister
POST /api/CancelRegistration
POST /api/StartCourseMaterial
POST /api/GradeExam

GET /api/SearchRegistrations
GET /api/SearchCompletions

As you can see, each of these messages expresses its intent. On the server, I read the name of the message from the URL. With the name I lookup a) how to parse the request into an object and b) the function to handle that message and generate a response. The handling function may read multiple entities or other APIs. It may also write to multiple places, send emails, etc. Whatever it needs to fulfill the use case. Since this is not directly exposing database tables as endpoints, a message-based API can be completely decoupled from the back-end data structure.

Messages as a first-class concept also leads to some interesting scalability options as your project grows. For example, messages can be queued or routed to other nodes.

Permissions are also pretty nice in this setup. Literally read the name of the message from the request URL, see if that item exists in the user's permission list. Since these operations are specific to the business logic, not to database operations, they are exactly scoped to the business needs. For example the business may want Instructors to be able to create courses, but without being able to set certain administrative parameters. I can make a message for CreateCourse with all options and CreateLimitedCourse with limited options. I have to write code for these different cases anyway!

Following the CQS principle, I also tend to categorize messages intended to produce side effects as Commands and messages asking for data as Queries. But that is just a further refinement to Messaging.

And by the way, there is nothing preventing you from using a REST endpoint for your CRUD operations and a message-based endpoint for behavioral operations.

Dive deeper into messaging in Part 2.

Posted on by:

kspeakman profile

Kasey Speakman

@kspeakman

collector of ideas. no one of consequence.

Discussion

markdown guide
 

Hi, this is an interesting post, but I am afraid this approach doesn't scale very well. In a simple REST approach every resource is isolated giving the developers the freedom to combine these resources in many different ways where this message approach seems to look very specific.
So I think using a REST api is more useful for big apps that would let developers use their granular resources in every way they want, while this message approach seems to fit to a very personal api, thus forcing other developers to only use the messages available.
That's why I think the approach doesn't scale well, because it's hard to create messages for every use case a developer might use.

 

I see where you are coming from, but this method doesn't have any more difficulty scaling compared to REST-based. (How you describe scaling, I think you are referring to Isolation.) It is not a function of how you package the requests and responses, but of how well the system boundaries are drawn. Message-based (especially for queries) can be divided up by responsibility area just like REST.

For writes, it does not make for a very developer-friendly application for things which logically belong together to be split up into multiple REST endpoints just because they are separate entities. It turns the API into a leaky abstraction of the underlying data model. It puts the responsibility on the client of knowing exactly which entities should be updated, in what way/order, and how to rollback if one fails. This is not a good experience for the consumer of the API. And they may not do it correctly.

Things which are logically separate can be on different endpoints (or nodes) in either REST or message-based. E.g. for message-based /api/sales/ChangePricing and /api/accounting/ApplyPayment.

Re: forcing developers to only use available messages.

Even with REST, you have to expose the API contract somewhere (i.e. documentation). REST does not handle requests until they are wired up to some handling code. It is no harder to create messages for every use case than it is to create REST endpoints for every use case. And using message-based doesn't mean you have to throw away everything (orthogonal but) typically associated with REST, like content negotiation and hypermedia. Use whichever combinations suit your problems best.

 
 

Your article is just chewing the CUD! What about the Read aspect? I find this a lot harder to represent with use cases because you end up with the API getting bogged down with presentation detail, or being too general and providing too much information for sensible bandwidth. Do you have any tips on getting that part of the API as succinct?

 

This is a thoughtful point. It is true that Read concerns can be drastically different from Write concerns. For example, full text search may be very important for reads, but is excess baggage when processing use cases (writes). No style of API is going to solve this problem for you, since it has more to do with the underlying data structures. But message-based does have a couple of tricks.

For example, SearchCourses { Search: 'foo', Page: 1, PageSize: 20 } might return multiple rows with only a few columns, just enough for displaying in a list. Whereas GetCourse { CourseId: 123 } might return the full details for exactly one course. Another one GetCourseDashboard {} might return several sets of statistical data. For APIs which service applications, I tend to tailor every (query) message and its returned data to the specific screen that uses it.

I can also tell you what I do to solve the data structure problem: I no longer try to use a one-size-fits-all data model. I create multiple models of the same data to suit different purposes. One of the models needs to be authoritative (probably the write model), but others (probably read models) can be projected off of that one. For example, shipping your data to Elastic Search for indexing. Another example is an executive summary report (which is just an aggregated version of the data). These are obvious cases, but also think about each of the queries I mentioned in the paragraph above being backed by their own tables.

 

I think that this is quite like GraphQL

 

Thanks for the comment. I suppose GraphQL could be a subset of a message-based API. My main point was that messages can help you represent business use cases at the API level instead of just data.

 

Totally, this is quite useful compared to the conventional way. Thanks for the good read.