The Four Interfaces of SaaS products

Jack Danger
Dangerous Engineering
8 min readOct 21, 2018

There needs to be a handbook for migrating a monolithic application into a service-oriented SaaS product suite.

The same things keep going wrong at every company: Moving code without moving the data that code needs, language proliferation, conflating synchronous and asynchronous work, not anticipating the need for backfilling data, etc.

This post is a guide to help navigate just one of the problems your team will face: Picking the right technologies for your four different interfaces.

Four? Yeah, four. A SaaS product suite eventually implements four interfaces for four different kinds of clients:

  1. The one your customers use
  2. The ones your employees use to manage customer data
  3. What you offer to third-parties to extend the value of your products
  4. The internal server-to-server connections that power your SoA

But that’s not how it starts.

When you’re first building your system you probably have a single application. Customers connect directly to this and it’s… well it’s your whole product. This is the code that has access to all of your data so when you need to build an admin view of your system you build it more or less right there where the data is.

This works great for the v1 of your admin interface. You didn’t have to duplicate a bunch of work and you can operate your product.

When you need a 3rd-party API you might build it on that same application as the other two interfaces or maybe you’ll put it off to the side because you’re starting to decouple your growing monolith.

By then you’ll have something like this:

Which is… well, it’s starting to get complicated. Those circles in the middle represent different applications. Managing all of those is hard. But what’s really going to bite you is that the authentication and authorization logic for at least two very different customer types is coupled with your legacy code in the original application. How do you provide an admin interface to these other applications that are showing up?

Well, you could add an admin interface to each application.

But this means you need to implement that authentication system in multiple places. And, depending on how you’ve built the admin UI, you may even need to ship the admin views in multiple applications.

Service-oriented architecture is notoriously hard to get right. Not because the technology is difficult to implement (it’s now a solved problem). SoA is tricky because when you have many places to put something it’s costly to have anything in the wrong place.

In a monolith putting some code in ‘./lib’ or ‘./models’ is mostly an aesthetic difference. But if you put some code in, say, the customer lookup service when it actually needs to be in the highly-available report-generating service you might have destabilized the whole system. If you’re moving to multiple services you’re not simplifying your system, you’re making it more complex and hoping it’s worth the tradeoff.

There is some core functionality in your product. It does something meaningful. If you conflate the different interfaces together you may have to implement that meaningful core functionality up to four times. But if you can clearly separate the authentication you can implement your core product value just once and provide windows to it through your authentication layers.

Put another way: if you can separate “who is this and which features can they use?” from the feature implementation then you can put all your effort into making the features themselves really good.

Four technologies for four interfaces

A common mistake when rolling out one of these interfaces is to reuse the technology from one of the others. Just because, say, HTTP + JSON is how you communicate with your customers doesn’t mean it’s the right choice for your internal service-to-service calls. And no matter what the other interfaces use your admin interface pretty much has to be a plain web app because that’s the cheapest thing to maintain.

Let’s review the needs of the different interfaces to help us pick a good technology for each one.

1. The first-party customer interface

The first API is the one you already have. No matter what your product does there is a frontend to your system that your customers use. This could be an HTTP-based HTML or JSON frontend. If you offer an email service maybe it’s over SMTP. If you’re Dropbox this is your own proprietary sync protocol. Whatever the transport system, your customers use your product through some kind of front door.

This API is one you control both sides of. You change server endpoints and client/display behavior in lockstep, creating a coherent user experience.

If you find your team calling this ‘the api’ you might be in trouble. There are three more to go.

A good technology for this is anything that your customers can use. This is where you sweat the UX until the customer experience is straight-up joyful.

The authentication layer for this needs to identify a single user and enforce that any data passing out of the system is data that is specific to that user.

In terms of actual implementation you have two choices:

  • You can have a highly-available frontend that performs the authentication and passes credentials along to your product code
  • You can have a highly-available authentication service that each service uses when responding to user requests.

In either case the authentication for a customer of your product is located in one place and separate from the actual features of your product.

2. The Admin Interface

Most companies build a half-assed version of this at first to gain any visibility at all into the system. Over time the admin interface becomes generally useful: Engineers diagnose production data problems with it, operations teams build their core workflows into it, and it becomes the primary tool for customer support.

The authentication layer here is more robust and needs to integrate with your IT system. You need to ensure only people who are currently employed at your company can access this and you need to audit everything anyone does — because through this interface a user can access the data of any user in your system.

The most scalable, sustainable way to build an admin system like this is a web application separate from the product services by a routing layer. This routing layer both performs authentication and translates HTTP+JSON or whatever the new web hotness is into standard service-to-service calls.

3. Third-party REST API

Most products will, at some point, benefit from offering a third-party API. Your customers get more value from you when they can opt in to extensions of your product.

This API basically has to be RESTful HTTP. That’s the standard that most other developers understand and it’s the easiest to document. Tools like OpenAPI let you generate clients for it automatically in any language. This interface must have clear versioned releases (with old versions supported indefinitely) because you don’t have control over the clients.

The implementation will look an awful lot like the admin one but with a different permission model. The routing layer will need to check if the 3rd party making a call is allowed to access a specific user’s data. This requires a complex system of registering 3rd-party applications, generating tokens, allowing users to enable/disable individual apps for their account, and rate-limiting. All of which would really complicate your product if this kind of logic leaked into core features.

4. The internal SoA

The fourth interface is fully internal — it’s the inter-process communication between server-side systems. You can use any technology for this but some are much friendlier to your engineers than others.

RPC: Verbs over Nouns

REST is all about nouns and it forces all operations to be reasonably simple. When you don’t know who’s using your API that’s a good fit. But when the other users of your API are your colleagues you need something more sophisticated.

An RPC (‘remote procedure call’) system allows your team to express their full intent, iterating on the API between services at the same cadence that they iterate on method and class signatures.

Structured schema

At some point your system becomes too large to run all of the different applications on a laptop. Before you reach that point you’ll benefit from implementing structured, strongly typed APIs between your internal services. You want to be able to automatically generate running mocks of dependent services (maintaining them by hand will fail) and you need to rely on the API type system in your unit tests to catch bugs. Something like Thrift or Protocol Buffers or Cap’n Proto will give you this natively.

Forwards- and backwards-compatible

You’ll make gradual changes to this API forever. How do you ensure that deploying one part of your system doesn’t introduce an API incompatibility between the new and the old code? How do you ensure that you can always read data encoded in a version of the API from last year with today’s API?

Tools (like the 3 mentioned above) ensure you can always read new data even in old clients (and vice versa) from any language.

Mutual SSL and Access Control Lists

Authentication and authorization for your internal interfaces is very different than the other three. You still need to ask “who is performing this action?” but the ‘who’ here isn’t a person, it’s a service. So you need some way to identify services (authentication) and some way to permit them to do stuff (authorization).

Here there’s likely an ACL where specific services are allowed to use specific endpoints. Defining permissions at this level of granularity is tedious but it lets you easily verify that low-security parts of your system aren’t gaining access to higher-security parts and it provides the means to generate a dependency graph of your services.

Focusing on your product

When your company is big enough your interfaces become products in themselves. But even while you’re small keeping them isolated at the edges of your system means you can get them right and you can more easily prove that they’re secure.

It also frees your team up to focus on the actual product work. When the interfaces are defined and separated from the core features then everybody — your customers, your employees, your 3rd-party partners, and other products— benefit from every improvement you make.

--

--