Review of ALPS

Application-Level Profile Semantics (ALPS) is a new format for describing web services. While it’s more limited in scope to defining the “what” of an API, it does promise some interesting gains in refocusing effort in the API design space. Today, we’re going to look at ALPS and outline its core features. We’ll also look at the new open-source ALPS Unified Utility, a new offering from its creators that promises to generate specifications from the definition.

What is ALPS? The API Tabula Rosa

ALPS is a format for describing web services. More specifically, ALPS is a format for designing a service by mapping each service’s operations and data elements. Essentially, ALPS provides a unified way to document the service in an agnostic format that is understandable regardless of the tooling, the perspective (client-side versus server-side), protocols, or architectures.

To take a brief detour, let’s discuss the concept of Bounded Context. In Domain-Driven Design, the Bounded Context is the focus of definition and understanding within a given structure. In the API space, the functions used to carry out a specific action are the same regardless of who is using them, as long as they are within the area of bounded context — this ensures that the context is maintained and understood in a variety of implementations. According to Mike Amundsen, the creator of ALPS, ALPS can be thought of in much the same way:

“ALPS does not make any requirements on URLs – that does not mean service implementors do not use URLs when the create a service based on an ALPS profile.

“how does one implement in a MVC framework to handle mappings for the API’s?” you can do this any way you wish. ALPS does not constrain implementations’ use of URLs. Your URLs are yours.

ALPS is designed to share common understanding across multiple implementations. for example an ALPS “todo” profile would contain all the data elements (title, id, completed) and actions (new, edit, markComplete, remove, listAll, listActive, listCompleted, clearCompleted) for the service. Basically ALPS can be a machine-readable version[0] of the human-readable TodoMVC functionality spec[1].

You can also think of ALPS as a machine-readable version of Eric Evan’s “BoundedContext” – the things that a service shares w/ the “outside world”

To quote Amundsen further, “ALPS tells you the WHAT of the service, not the HOW”. This is vitally important, as the complexity of a system can often cloud its purpose — we can get to absorbed in the actual mechanics of how an API does something without actually walking away with an understanding of what it does. In this way, ALPS can be thought of as the “Tabula Rosa” of APIs — a unifying methodology by which an API could be translated and understood across multiple formats, instances, and deployments.

How ALPS Works

To start, ALPS defines the problem domain boundaries (as defined in the documentation, the “possible data elements and possible state transitions”). For APIs, ALPs defines possible identifiers for data values by using profiles. In this way, if a service supports a profile (say, “glogging”), all data values (such as “givenName) and transitions (such as ”publish”) should be commonly understood. These profiles can be advertised through HTML responses using several methods:

  • Link Header: The Link header, in combination with the href and rel values, can direct users to the profile (example: Link: https://alps.io/profiles/microblogging; rel="profile").
  • HTML4 Profile Attribute: The profile attribute on the HTML.HEAD element can provide the relevant data to users; additionally, the documentation notes that the "profile" attribute value points to the ALPS profile itself (example: <head profile= "https://alps.io/profiles/microblogging">…</head>)
    HTML5 Profile Element: HTML5 documents utilize the Link element alongside the href and rel values to link to the profile (example: <link href="https://alps.io/profiles/microblogging" rel="profile" />)

HTML Mapping

The mapping of ALPS profile implementation to HTML is rather straight-forward. The documentation notes a few specific valid examples. First, it notes the following for applying descriptor.id ALPS data to HTML elements:

<!-- ALPS document -->
<descriptor id="abc" ... />

<!-- HTML read-only elements -->
<div class="abc">...</div>
<span class="abc">...</div>
<li class="abc">...</li>
<p class="abc">...</p>

<!-- HTML state transition elements -->
<input type="text" class="abc" />
<a href="..." class="abc" />
<img src="..." class="abc" />

As you can see, by stating the descriptor ID in this way, you can utilize it as classes across multiple elements. When invoked in this way, it transforms the descriptor.id into a class that can be referenced where appropriate. The documentation also notes that you can utilize the descriptor.name property to create a class which calls the name instead, as follows:

<!-- ALPS document -->
<descriptor id="abc" name="xyz" ... />
<descriptor id="abc2" name="xyz" ... />

<!-- HTML read-only elements -->
<div class="xyz" data-alpsid="abc">...</div>
  <span class="xyz" data-alpsid="abc2">...</div>
</div>

<!-- HTML state transition elements -->
<input type="text" class="xyz" data-alpsid="abc2"/>
<a href="..." class="xyz" data-alpsid="abc"/>

The mapping that ALPS applies is relatively simple, but you should stick with a consistent convention. As long as this convention is the same throughout the definition, that same banded context justification will carry through, making it clear what the API does and where those profile attributes live in the definition.

An Example Application

Let’s look at a simple example. This is a sample user account profile in the documentation, and it represents the clarity that ALPS can deliver.

<alps>
  <link rel="self" href="https://alps.io/profiles/useraccount" /> <1>

  <!-- data elements --> <2>
  <descriptor id="user" type="semantic" />
  <descriptor id="accessCode" type="semantic" />
  <descriptor id="givenName" type="semantic" />
  <descriptor id="familyName" type="semantic" />
  <descriptor id="email" type="semantic" />
  <descriptor id="telephone" type="semantic" />

  <!-- transitions --> <3>
  <descriptor id="list" type="safe" /> <4>
  <descriptor id="detail" type="safe" /> <5>
  <descriptor id="login-link" type="safe" name="login" /> <6>
  <descriptor id="login-form" type="unsafe" name="login" /> <7>
  <descriptor id="create-link" type="safe" name="create"/> <7>
  <descriptor id="create-form" type="unsafe" "name="create/> <8>
  <descriptor id="update-link" type="safe" name="update"/> <9>
  <descriptor id="update-form" type="idempotent" name="update" /> <10>
  <descriptor id="remove-link" type="safe" name="remove" /> <11>
  <descriptor id="remove-form" type="idempotent" name="remove" /> <12>

</alps>

First, we have the directive for where the profile can be found here:

  <link rel="self" href="https://alps.io/profiles/useraccount" /> <1>

From here, we start to state the data elements:

  <!-- data elements --> <2>
  <descriptor id="user" type="semantic" />
  <descriptor id="accessCode" type="semantic" />
  <descriptor id="givenName" type="semantic" />
  <descriptor id="familyName" type="semantic" />
  <descriptor id="email" type="semantic" />
  <descriptor id="telephone" type="semantic" />

Note the type here is stated as "semantic". In ALPS, four core values can be used: safe, unsafe, idempotent, and semantic. We’ve discussed these nuances at length previously on Nordic APIs, so we suggest reading through that article if you need to refresh your understanding of idempotency. In short, something is idempotent if it gives the same result every single time, regardless of the change (such as always removing an element, regardless of what is invoking the request).

Here, we created a few data elements: user is quite obviously the user ID, accessCode is the code used to login, etc. Now that we have these data elements, we must state the transitions that will act upon them:

  <!-- transitions --> <3>
  <descriptor id="list" type="safe" /> <4>
  <descriptor id="detail" type="safe" /> <5>
  <descriptor id="login-link" type="safe" name="login" /> <6>
  <descriptor id="login-form" type="unsafe" name="login" /> <7>
  <descriptor id="create-link" type="safe" name="create"/> <7>
  <descriptor id="create-form" type="unsafe" "name="create/> <8>
  <descriptor id="update-link" type="safe" name="update"/> <9>
  <descriptor id="update-form" type="idempotent" name="update" /> <10>
  <descriptor id="remove-link" type="safe" name="remove" /> <11>
  <descriptor id="remove-form" type="idempotent" name="remove" /> <12>

Here, we have stated a variety of functions, each of which can act upon the data elements. Note that the server selects which data elements can be used and which transitions can be undertaken against them. Again, ALPS defines the “what”, not the “how”. We can see from this simple implementation that any service utilizing this profile will be using the same data elements and transitions. This is the power of ALPS, a portable, easy to understand data format for understanding the contextual functions of the API codebase.

The Unified Utility

So this is all well and good, but one may wonder what the practical benefit of utilizing such a definition actually is. Enter the ALPS Unified Utility. A relatively new release, the ALPS Unified Utility is a simple utility that takes valid ALPS description documents and turns them into API definition documents. The release announcement notes a wide variety of possible document types such as ProtoBuf, AsyncAPI, OpenAPI, and more. In essence, the Unified Utility is a step in taking the definition of “what” and converting it into the “how”.

A big change in thinking that this enables is what Amundsen likes to call design-first. The idea is to design the API around your elements, and then figure out the actual implementation of those elements later. Too often, the API space does this somewhat inverted — there is a general idea of what the end product should be, but the output is shaped heavily by what the generated code takes shape to be. In this new approach, Amundsen suggests creating the ALPS definition and letting the API build itself.

While this tool is still very much in its infancy, it does show some promise. The idea of building around the design-first has been a common thought process in recent years, so seeing a description format take this head-on is interesting. Whether or not it will do what it promises is yet to be seen, but if it does indeed deliver, it could make development faster, easier, and more design (not code) centric.

Conclusion

ALPS is not a perfect solution — there will be some who find it overly simplified, and its reliance on definitions between either data type or transition may seem too simple to represent complex interactions. Remember, though, that the idea is to explain the “what”, not the “how” – for that limited purpose, it delivers well, even if that delivery is necessarily limited by its own scope. It will be interesting to see what happens with the ALPS Unified Utility, and if the project continues in a strong direction, it could be a contender for API creation in the near future.

What do you think about ALPS? Do you think it’s powerful enough for complex interactions? Let us know in the comments below!