Introduction

Well-known Identifiers are predictable identifiers or location descriptors, by which humans or machines can find certain files, executables, services, or resources. They allow a human or computer to discover affordances within a given context, which expose a well-known interface, without the need of additional out of band information.

The description of this pattern is inspired by Well-Known Uniform Resource Identifiers, by recognizing that this is a more general pattern. Once you start looking for it you will discover it in very different kinds of contexts.

Some of the examples may seem trite and obvious, but we think it's useful to recognize the common pattern across different contexts, to consider why this pattern works (for technical and cultural reasons), and to consider in which other contexts this pattern could be applied but is not yet common practice.

Tensions

Computer systems consist of many different software components. These components expose affordances, which can be used by other components, or by human operators. Examples of affordances are the awk interpreter, a HTTP endpoint, a function in a library, or the Linux kernel API. These are all resources the environment may offer to the programmer and their program.

These affordances need to be discovered. The programmer can do so by consulting documentation, or using introspection facilities of the environment. The specifics can then be encoded inside the program. This however makes the program rigid and brittle. These specifics can and will change, or differ across contexts (systems), so instead we make them configurable, so the program makes fewer assumptions, and thus can be more easily adapted to a new context or environment.

But this reduces convenience. Before the program "just worked" (within a narrow context), the new program can operate in a broader context, but only after appropriately configuring it. Thus a tension arises.

Well-known identifiers resolve this tension by allowing the program/programmer to make certain assumptions safely, because there is an agreed upon convention for where to discover certain affordances.

For the developer they resolve a tension between novelty and familiarity, allowing existing knowledge to transfer to new software, because new functionality is provided through a familiar interface.

Pattern Description

Given a directory or namespace lookup system (file system paths, URL paths, namespaces of symbols and function, etc), if there is either a documented standard or a strong cultural convention that a certain type of affordance (abstract interface) is available under a specific identifier, then this forms a Well-known Identifier.

Well-known Identifiers are resolved relatively to a given context, like the current filesystem, a HTTP origin, or the current classpath. In this sense they provide an abstraction. Each context can decide what to provide at that identifier (location), as long as what is provided implemements the expected interface or format.

Well-known Identifiers are both a technical and a cultural pattern. Technically they complement the idea of "programming to an interface, not to an implementation". They answer the question of where to find, or how to access, the interface. They allow for service discovery, and form a type of ConventionOverConfiguration.

How "well-known" an identifier is is a sliding scale. Often only a subset of existing systems will use a well-known common name, while others use other arbitrarily chosen names, leading to unnecessary friction in terms of interoperability. It is the role of technology leaders to push for adoption of well-known identifiers within the projects they lead, as well as to recognize the potential of coining identifiers in new areas, and to advocate for their common use.

Some well-known identifiers are standardized through standard bodies like the IETF or W3C, however the existence of a standard does not say anything about its adoption, neither does the lack of a standard imply a lack of common use. Ultimately adoption is a cultural problem, but the existance of a documented standard can certainly help in gaining adoption.

Examples

On POSIX-compatible operating systems /bin/sh is a standard identifier. So is /usr/bin/env, but /bin/bash for instance is not. (Some systems use /usr/bin/bash). Note that even though these conventions have been common practice for decades, it is still possible to find UNIX style systems that don't adhere to them.

On the web there are a number of well-known URIs standardized through RFC 8615, under the /.well-known path prefix, like /.well-known/webfinger or /.well-known/caldav. Another one is robots.txt defined in RFC 9309.

There are standard TCP ports by which certain services can be found, like port 80 for HTTP, 443 for HTTPS, or 25/587 for SMTP. In a sense they are also an application of this pattern.

Certain JVM libraries and tools will look for their configuration on the classpath, for instance Logback tries to find a logback.xml. While this is an identifier that's specific to one tool, it's a mechanism that could be leveraged more generally.

Similarly JVM system properties that are understood by mutiple libraries/tools seems to be an underutilized mechanism.

There are several commonly used well-known environment variables. Most software intended to run in a cloud environment understands the PORT variable to decide which HTTP port to run on. Most CI environments set the CI variable to signal that the code is being run on CI.

Use for Clojure tooling

These standard identifiers are important for machines and programs, but they can also be important for humans. Gaiwan does a lot of projects, for clients and open-source, and we want people to feel comfortable going from one project to another. That's why we have our own set of standard identifiers that we set up on every project.

  • bin/launchpad - start development environment
  • bin/kaocha - run tests
  • (user/go) - start the application
  • (user/browse) - open the application in a browser

Other common identifiers

  • (user/reset) - use tools.namespace to do a reset/reload of changed namespaces
  • (user/reset-all) - use tools.namespace to do a reset/reload of all namespaces
  • (user/portal) - launch the portal UI

Some of these we have adopted based on earlier precedent in the Clojure ecosystem, some of these we have coined ourselves. In both cases we advocate for their adoption and use across the ecosystem.

The first two, bin/launchpad and bin/kaocha, are worth highlighting. We strongly encourage anyone adopting these tools (Launchpad and Kaocha), to create these two executables within their projects.

We sometimes get questions about that. Why not use clj -X:kaocha, or bb run launchpad. The problem with these is that they are not general enough. Not every project uses Clojure CLI or Babashka. By having an executable path that is independent of the concrete tooling we create an abstraction. From the programmer's point of view they can invoke Kaocha the same way on any project. The same goes for tooling, like CI, which gets a standardized way to invoke Clojure tests.

What goes into these executables can be adjusted to the needs of the project. Besides invoking the appropriate Clojure launcher, they can perform additional steps. For instance, it's common for bin/kaocha to run npm install when needed.

In Gaiwan's Corgi Setup we have the key combination ,, to evaluate a snippet that's stored in a register, and we set some registers so we can easily invoke some of the above.

  • ,,g - runs (user/go)
  • ,,b - runs (user/browse)
  • ,,p - runs (user/portal) in clj
  • ,,P - runs (user/portal) in cljs

Further reading

History

2024-05-20

  • First public version (Arne Brasseur)