Meshing a modern monolith
In my last post, I described a model for a new type of web service monolith, the SUFA design pattern. A key part of Simple, Unified, Function-based Applications is the fact that the unit of compute is actually individual functions running on a job scheduler. The functions are composed to create the business logic of the application, and a framework is responsible for handling requests and running the functions as described.
One design consideration is ensuring that SUFA systems are able to scale efficiently, and this is accomplished with capability groups and meshing. I said that these topics deserve their own post, so here we are! The purpose of SUFA is to take the best elements of different design patterns to make something that is powerful, but never complicated. The ease and simplicity of building a monolith combined with the scaling characteristics of serverless or microservice systems is the ultimate goal.
Capability groups take full advantage of the decoupled functions within a SUFA system. Each function has some associated metadata, one of which is a namespace. A namespace is a logical grouping of functions, such as auth, cache, billing, db and others. Functions within a namespace are meant to encapsulate a category of logic, and require a common set of resources such as access to a particular data store, or secrets for a third party API. When a SUFA system is deployed, a single deployable is built, and then one or more Auto-Scaling Groups (ASGs) manage a set of instances to handle changes in traffic.
A simple SUFA system with one namespace (such as a simple REST API with a single database) could be easily deployed with a single ASG. In order to scale with increased traffic, a key property of microservice systems is that individual services can scale independently to handle endpoints and functionality that are being used more than others. With a traditional monolith, any increase in traffic requires scaling up instances, no matter which endpoints are getting the majority of the spike.
With SUFA systems, multiple ASGs are created, each designated as a capability group. Each capability group is given access to the resources required for the associated function namespace to operate (such as the datastore or secrets), and can then scale independently of one another. Since the application's functions are decoupled entirely from one another, it's possible for some functions to run on the host that receives the request, and functions from particular namespaces to be meshed into other capability groups. A SUFA framework such as Atmo is responsible for handling the meshed communication, completely absorbing the complexity.
Adopting this model of meshing request handling among multiple capability groups has several advantages. The individual groups can scale independently, allowing you to more flexibly handle increases in traffic without needing to scale up instances to only handle a sliver of functionality. It has desirable security properties such as "edge" instances handling incoming requests but only granting access to sensitive resources to particular capability groups that are not directly available on the public internet.
To give a more concrete example, imagine a blogging platform (like Hashnode where this blog is hosted!). When an author requests their blog's analytics, a SUFA system could describe functions such as:
In this example, the
auth namespace needs access to a cache, the
db namespace access to a PostgreSQL instance,
analytics needs access to a third-party service, and
frontend needs access to static object storage. The SUFA system would be deployed with capability groups that grant access to said resources, allowing each to scale as needed when the various parts of the app experience increased load. The SUFA framework would automatically mesh instances in each group together to collaboratively handle the request and schedule the correct functions on an instance in the correct group.
Since all of this behaviour is provided by the SUFA framework, developers still need only concern themselves with writing the functions and defining how to compose them. Developers can even run a single instance in their local development environment, and then production environments can run in capability groups. Atmo runs functions compiled to WebAssembly modules and uses the Grav messaging mesh to provide meshing functionality, which provides the flexibility and simplicity needed to build powerful applications that are never complicated.
Cover photo credit: Zak Podmore/The Salt Lake Tribune