Typically when we think about applications we structure them by business functions that will be implemented based on given, quite generic programming models such as, broadly speaking, Web applications and background jobs. These serve as entry points to our internal architecture comprising of, say, services and repositories.
Any code base that grows requires modularization along its abstraction boundaries if it wants to stay manageable. Contracts between modules are expressed as APIs. We tend to think about APIs as “access points” to methods that the “API consumer” invokes:
But that’s only the trivial direction. It is typical that some work be delegated to the API consumer to fill in some aspect. For example streaming some data, handling some event, consuming some computation result. That is: It is not only the subsystem that implements the API, but the consumer does as well:
In the still simple cases, the APIs implemented by the consumer are passed-on callbacks (or event handlers, etc.). This is by far the most prominent method employed by your typical open source library.
If callbacks need to be invoked asynchronously, or completely independently from some previous invocation, e.g. as a job activity on some other cluster node, this approach becomes increasingly cumbersome: In order to make sure callbacks can be found, they need to be registered with the implementation before any invocation need occurs.
In short: You need to start seriously about how to find and manage callback implementations in your runtime environment. That is, switching to the term extension interfaces, you have a serious case of Extend me maybe….
Being an API that may be used by a probably yet undefined number of consumers, other aspects may require rules and documentation such as
- The transactional environment propagated to extension interface invocations.
- Is there authorization constraints that have to be considered or can be declared by implementors?
- Is there concurrency/threading considerations to be considered?
And that is where it starts becoming worthy to be called a programming model.
While this looks like just another way of saying callback or extension interface, it is important to consider the weight of responsibility implied by this – better early than later.
For a growing software system, managing extensions in a scalable and robust way is a life saver.
Not considering this in time may well become a deferred death blow.