This article includes diagrams that may cause issues with screen readers, and are unlikely to be legible on small screens.
A couple of months ago I was writing some educational material about multitenanted web applications, and I decided to try and define a handful of architectural patterns for multitenancy. During this, I was writing about tenant identification, and how the process of identifying a tenant for any given request, is virtually identical to the process of identifying a user.
This lead me to consider that the underlying process of these two things, is actually a pattern in its own right, and it can be applied to many other similar situations. After much research, I discovered that while there are similar patterns, there are none that define this specific, well used process. So I decided to try and define it myself, and this article is the result of that.
Defining a Pattern
Before I fully introduce you to the Request-derived Context pattern, I wanted to talk a little about defining a pattern. Both design and architectural patterns will typically already exist, before the individual defining them does so. The majority of the patterns from the Gang of Four, or Martin Fowler, were already in use, they just didn't have a name, so lacked any sort of formal vocabulary or terminology.
I want to make it abundantly clear that I am not claiming to have invented the concept this pattern defines. I am simply trying to give it a name, and a shape, so that it can be discussed, and understood by others.
Request-derived Context
This Request-derived Context pattern is an architectural pattern, so it models a process and the general structure of a system within an application. The process it models is that of retrieving the relevant context for a given HTTP request. The context itself is derived from the request, hence the name.
The idea is that an incoming request contains a context source, which is used to derive the context. That source is extracted by a context extractor, and then consumed by a context resolver, which retrieves the context. The context is then persisted for the duration of the request in a context store.
If you were to visualise the sequence/flow of this process, it would look something like this.
The above description and diagram may not be all that clear, however, I can assure you that almost every one of you will be familiar with this process, at least in part. Even though, to the best of my knowledge, I'm the first one to attempt to define this pattern, it is something that has been used for decades. There are, in fact, two very common usages of this pattern, server-side sessions, and user authentication.
Server-side Sessions
The chances are, that if you're reading this, you're a Laravel developer, or at the very least, a PHP developer. If so, you're most likely familiar with the concept of server-side sessions, typically just called sessions. Some of you may even be familiar with how they work under the hood, but for those who aren't, let me explain.
When a user visits a website, such as an ecommerce store, a session will be created for them. This session is used to store the state for that visitor which allows it to persist across requests. However, since HTTP is a stateless protocol, the server has no way of knowing who the user is on subsequent requests. To solve this, the server assigns each session a unique identifier, referred to as a session ID. This session ID is provided to the client in the response of the first request, and is then sent back to the server on every subsequent request.
The cookie itself would typically be encrypted, but I've left that out for clarity.
Within Laravel, the context source is the laravel_session
cookie,
which is extracted by the StartSession
middleware (the
context extractor), and then both resolved and stored by the
the SessionManager
class (the context resolver
and context store).
User Authentication
Another common example of this pattern is user authentication. When a request is received by the server, something within the request will identify the current user, allowing the server to know who is making the request. There are lots of different methods of authentication, but the most common uses sessions, and actually builds upon the process above.
When dealing with sessions, the session ID that comes from the session cookie, can be used directly to
resolve the sessions. This means that the session_id
cookie is known as a direct context
source. However, when dealing with user authentication via session, the session ID becomes a users
indirect context source. To resolve the user, you must first resolve the session, and then
locate the user ID stored within it.
We can visualise this by adding to the session diagram from above.
As mentioned above, the context source is still the session_id
cookie, but in this
situation the context extractor has the additional step of resolving the session, and then
extracting the user ID (the direct context source). Then the user which is the
context, is resolved using a UserProvider
, which serves as our
context resolver.
In a modern web application, dependency injection would be used, so the context extractor would actually depend on the session, so it wouldn't need to resolve it itself. Both the user context, and the session context, would also likely be lazy loaded, so they would only be resolved when they are actually needed.
Within Laravel, users are resolved only when they are needed, and the context extractor is the
relevant Guard
class that's configured for the relevant auth type. The
guard contains an instance of the UserProvider
class, which functions as
the context resolver. It also keeps track of the user context, making it also the
context store. Laravel does also offer remember
me functionality, which provides a second context source for the user, should the
session have expired.
There are obviously other methods for user authentication, such as API or JWT tokens in the
Authorization
header, dedicated cookies, or even custom headers. Whatever the method employed,
the process is still the same, as the context source is contained within the request.
And So On and So Forth
Server-side sessions and user authentication are just two examples of the pattern, and I used them as they're
ones that most of you will be familiar with. However, the pattern can be applied to anything. Have a web
application that has multiple sites on different domains or subdomains? The domain, or the subdomain becomes
the context source, whether it's multitenanted or not. Does your web application have a
hierarchical URL structure, so when accessing a URL prefixed with /project/my-project
, everything
after is scoped/related to the project my-project
? Well, that portion of the URL is the
context source.
This is a versatile pattern that can be applied to many situations, and is actively being used. It's honestly surprising that it doesn't have a name, or any sort of formal definition. Hopefully this article will help you understand the thought process that lead me to try and define one.
The Components of The Pattern
In the examples above I mentioned the individual components of the pattern, and even included a diagram that shows you the flow of this process. Hopefully, the names of each component, as well as with the added context (no pun intended) of the examples, have made it pretty clear what they each do. However, I'd like to expand on this with a more formal description and definition of each component, if only for clarity sake.
Context
The context isn't really a component, and it's just about a role, but it's important to understand what it is. The context is the information that is derived from the request, and can be anything, whether it's a session, user, tenant, locale, project or quite literally anything else. It doesn't have to be a class/object, it can be a simple value, but will often be one.
The context will define the bounds that the handler of a request will operate within, which can affect the data is available, the actions that can be performed, and the overall behaviour of the application. Because of this, requests should only ever have one active context for a given context type. If a request should operate within the context of multiple users for example, then that's not the user context, that's some sort of hierarchy or relationship context, and should be treated as such.
Context Source
The context source is any value, or piece of data that exists within a request, that can be used to derive the context for that request. This means that the context source can be included in pretty much every part of the request, such as.
- Cookies
- Headers
- URL Components
- Request Body
There are two types of context source, the first is known as direct, and refers to values that can be used directly to resolve the context. The second is known as indirect, and refers to a value that would typically require some sort of lookup or resolution to retrieve a direct context source. I'll admit, that the line between the two is a little blurry, as it really depends on what exactly is being resolved.
For example, a session ID cookie that's encrypted, would be considered a direct context source
for the session, even though it requires decryption. However, a JWT token in the Authorization
header, would most likely be considered an indirect context source, because it has to be
decoded, parsed, and then the user ID extracted from it. The distinction between the two is not particularly
important, and only really matters when writing or talking about an application that has both.
To make things a little more complex, a direct context source would only ever be the source
for one particular type of context. Whereas, an indirect context source, could be the source
for multiple types of context. A direct could also be an indirect context for
other types. In the examples above, the session_id
cookie was the source for both the session,
directly, and the user, indirectly. That same cookie could also be the indirect context source
for the Cart
, the Wishlist
,
or any other context that could be derived from the session.
Again, don't get too hung up on the detail of what exactly is a context source, and its different types. It’s simply a term used to refer to values that serve this specific role.
Context Extractor
The context extractor is a class or function that is responsible for extracting the context source from the request. Its purpose is to find the direct context source for a given context, even if that includes a lookup or some other form of processing. They do not need to be distinct from other components, they are more of a role than a separate class or function. Each type of context should have its own context extractor, whether that's a totally separate class or function, or a generic approach with arguments to specify the context type.
As mentioned in the examples earlier on in this article, within Laravel, the
StartSession
middleware is the context extractor for the session context, and the
Guard
class is the context extractor for the
authentication context. However, the StartSession
middleware extracts
the session ID, and tells the session manager what it is, but doesn't actually start any resolution or lookups,
so functions as only a context extractor. The Guard
class, on the other hand, extracts the user ID from the session, resolves the user, and then stores it,
functioning as a context extractor, context resolver and
context store.
Context Resolver
The context resolver is a class or function that is responsible for resolving the context from a direct context source, extracted from a request by a context extractor. Much like the above, the context resolver is more of a role, and as such does not need to be distinct from the other components. It is quite common for a single class to be either a context extractor, and context resolver, or a context resolver and a context store. Context resolvers are also per context type, whether generic or specific.
Context Store
The context store is a class responsible for keeping track of the current context for the duration of the request. A context store can be per context type, but when dealing with context that can be mapped to classes, a generic context store could be used.
Again, the context store is more of a role than a distinct class, so its implementation and combination with other components is entirely down to the developer. It's not required, but it is recommended that your context store is capable of distributing the context and its source to the relevant places. This can include things like globally setting the default value for a route parameter, or adding a cookie or header to the response.
A Real Life Example
I've covered a couple of different implementations of this pattern within Laravel, but I'd also like to point you towards a more apparent example of it. I'm referring to my multitenancy package for Laravel, Sprout.
The context extractors in Sprout are implementations of the IdentityResolver interface. Out of the box it supports five different types of context source, subdomains, headers, the URL path, cookies and sessions. The first four are direct context sources, and the final one, the session approach, is indirect. These are all generic context extractors that require values to configure them, such as the header, cookie or route parameter name.
Once the context source has been extracted, it is passed to a context resolver , which is an implementation of the TenantProvider interface. The package comes with two generic implementations, one for Eloquent, and one just using the database.
Finally, the context store is an implementation of the Tenancy interface. In sprouts implementation, this class also contains the context resolver, and sort of functions as a bit of a proxy context resolver.
In Sprout, the context is a tenant, but you would refer to it as whatever the implementation is. This is because it allows for multiple tenants to be active at the same time, though only one of each type, to allow for child tenants, or other hierarchical relationships.
An Example Implementation
I've covered everything that I wanted to cover, but before I finish and leave you to ponder on this, I'd like to show you a simple implementation of the pattern. I'd go as far as to call this example a pure, modern implementation, as it leans more into the separation of concerns, and dependency injection, than most implementations you'll find in the wild.
This whole implementation starts with a Request
class, which is a pure
immutable representation of a HTTP request. It contains everything that comes with the request, like the cookies
and headers, but it does not know anything about the route, sessions or a user. I'm not going to give example
code for this class, as it would be huge.
This example code makes use of static analysis generics. If you're not familiar with them, you can find out more here.
The Contracts
For this implementation to function there needs to be two contracts (interfaces), that define the structure of a context extractor, and a context resolver.
/**
* @template TSource of mixed
*/
interface ContextExtractor
{
/**
* @param Request $request
* @return TSource|null
*/
public function extract(Request $request): mixed
}
First up is the context extractor, which defines a single method,
extract
, that takes a
Request
, and returns a mixed
value. I'm also creating the generic template TSource
, which defines
the data type the context source will be.
/**
* @template TSource of mixed
* @template TContext of object
*/
interface ContextResolver
{
/**
* @param TSource $source
* @return TContext|null
*/
public function resolve(mixed $source): ?object
}
Next is the context resolver, which again defines a single method,
resolve
, which takes a
mixed
argument, and returns an
object
or null
.
Here I define two custom types,
TSource
again, which defines the type that the context source will take,
and TContext
which defines the type of the context that will be
resolved.
The Context Manager
In this implementation I'm adding a context manager that functions not only as a context store for all types of context, but also as a manager to facilitate the handling of context.
final class ContextManager
{
private Request $request;
/**
* @var array<class-string, ContextExtractor>
*/
private array $extractors = [];
/**
* @var array<class-string, ContextResolver>
*/
private array $resolvers = [];
/**
* @var array<class-string, object|null>
*/
private array $context = [];
/**
* @template TContext of object
* @template TSource of mixed
*
* @param class-string<TContext> $type
* @param ContextExtractor<TSource> $extractor
* @param ContextResolver<TContext> $resolver
*/
public function register(string $type, ContextExtractor $extractor, ?ContextResolver $resolver = null): self
{
if ($resolver === null && ! $extractor instanceof ContextResolver) {
throw new \InvalidArgumentException(
'If no resolver is provided, the extractor must implement ContextResolver.'
);
}
$this->extractors[$type] = $extractor;
$this->resolvers[$type] = $resolver ?? $extractor;
return $this;
}
public function setRequest(Request $request): self
{
$this->request = $request;
return $this;
}
/**
* @template T of object
*
* @param class-string<T> $type
*
* @return T|null
*/
public function context(string $type): ?object
{
if (array_key_exists($type, $this->context)) {
return $this->context[$type];
}
if (isset($this->extractors[$type])) {
$extractor = $this->extractors[$type];
$source = $extractor->extract($this->request);
if ($source === null) {
$this->context[$type] = null;
} else {
$resolver = $this->resolvers[$type];
$this->context[$type] = $resolver->resolve($source);
}
} else {
throw new \InvalidArgumentException(sprintf(
'No context registered for type \"%s\"',
$type
));
}
return $this->context[$type];
}
}
This is quite a big class, so let's look through each bit individually.
/**
* @var array<class-string, ContextExtractor>
*/
private array $extractors = [];
/**
* @var array<class-string, ContextResolver>
*/
private array $resolvers = [];
/**
* @var array<class-string, object|null>
*/
private array $context = [];
The extractors
property is an array that contains a mapping of the
context type to an instance of ContextExtractor
. Likewise, the
resolvers
property is the same, except it maps context type to
instances of ContextResolver
.
The context
property is actually the backing for the
context store, storing a map of context type to its current context, which could possibly be
null
.
/**
* @template TContext of object
* @template TSource of mixed
*
* @param class-string<TContext> $type
* @param ContextExtractor<TSource> $extractor
* @param ContextResolver<TContext> $resolver
*/
public function register(string $type, ContextExtractor $extractor, ?ContextResolver $resolver = null): self
{
if ($resolver === null && ! $extractor instanceof ContextResolver) {
throw new \InvalidArgumentException(
'If no resolver is provided, the extractor must implement ContextResolver.'
);
}
$this->extractors[$type] = $extractor;
$this->resolvers[$type] = $resolver ?? $extractor;
return $this;
}
Next up is the register
method, which allows for the
registering of a context with its context extractor, and
context resolver. Since extractors and resolvers don't have to be distinct, this method
allows you to provide a single object that implements both
ContextExtractor
and
ContextResolver
. It also uses the generic types
TSource
and TContext
.
private Request $request;
public function setRequest(Request $request): self
{
$this->request = $request;
return $this;
}
There's also a setRequest
setter method, which
populates the request
property. The idea is that once the
Request
object is created, the
ContextManager
is given an instance of it, so that it can facilitate,
or rather, manage, context.
/**
* @template TContext of object
*
* @param class-string<TContext> $type
*
* @return TContext|null
*/
public function context(string $type): ?object
{
if (array_key_exists($type, $this->context)) {
return $this->context[$type];
}
if (isset($this->extractors[$type])) {
$extractor = $this->extractors[$type];
$source = $extractor->extract($this->request);
if ($source === null) {
$this->context[$type] = null;
} else {
$resolver = $this->resolvers[$type];
$this->context[$type] = $resolver->resolve($source);
}
} else {
throw new \InvalidArgumentException(sprintf(
'No context registered for type \"%s\"',
$type
));
}
return $this->context[$type];
}
Finally, we have the context
method, which returns
the context for the given type, or null
. This method
is a little more complex than the others, so lets break it down further.
if (array_key_exists($type, $this->context)) {
return $this->context[$type];
}
If there's already an entry in the context
property, return that
value. Pay special attention to the fact that this is using the
array_key_exists
function, and not the
isset
construct. This is because if the context
was requested previously, and not found, the current context for that type is
null
, and
isset
would return
false
here, even though there is an entry in the array.
if (isset($this->extractors[$type])) {
$extractor = $this->extractors[$type];
$source = $extractor->extract($this->request);
if ($source === null) {
$this->context[$type] = null;
} else {
$resolver = $this->resolvers[$type];
$this->context[$type] = $resolver->resolve($source);
}
} else {
throw new \InvalidArgumentException(sprintf(
'No context registered for type \"%s\"',
$type
));
}
If we hit this point, the context isn't already stored, so we need to attempt to find it. First we check
that the context type is registered, which can be done with an
isset
on the
extractors
property. If it is not registered, we throw an exception,
otherwise we continue by passing the Request
object to the
context extractor, to retrieve the context source.
If there's no context source returned, we can assume that this is no context of this type,
and set the context to null
in the
context store. If there was one however, we will need to resolve it using the
context resolver, adding its return value to the context store, regardless
of whether it was null
or not.
I'm aware that if you're using static analysis on this, it's going to complain that there's no check to see
whether there is a
resolvers
property. It is safe to assume that it is there if there
is
an extractor, but you could throw in a check here if you wanted to be extra safe.
return $this->context[$type];
Once all the above is done, we return the context from the "context store", regardless of whether it was resolved. It's safe to assume that the context is present in the array, as the above would have ensured an entry was present.
Consuming the Implementation
Let's assume that we have the following classes in our codebase.
Route
- Represents the route handling the current request.Session
- The server-side session.User
- The currently authenticated user.Tenant
- The tenant the current request exists within.
I'm going to assume that there's a dependency injection solution in place, because honestly, if you aren't using one, this particular implementation is a bit much. This solution, whatever it is, would need to be configured to know the following.
-
When
ContextManager
is resolved, callsetRequest
with the currentRequest
. -
When attempting to resolve
Route
,Session
,User
, orTenant
, callcontext
onContextManager
.
You could even use attributes or contextual injection methods, dependant on the solution being employed to handle dependency injection.
The Purity of the Implementation
At the start of this section I referred to this implementation as being modern, but also pure, which I know is quite the claim, but let me explain what I mean.
This implementation of the pattern provides a clean
separation of concerns, not just between the components of the pattern itself, but between the components of
the application. The Request
class is immutable, and represents an
HTTP request, which means it can be used for both incoming, and outgoing requests.
On top of this, the concept of a route, session, user or tenant has been abstracted away without introducing
a whole host of inherent complexity. The HTTP layer in the application logic doesn't need to care about how
those concepts are used, and nor does the controller and the rest of the business logic. Those concepts
have been abstracted down into context
, presented by a class that can be depended on. Code
that needs the route can depend on the Route
class, without depending
on code that's responsible for resolving it. The same goes for the session, user and tenant.
Even if you weren't using dependency injection, or were using a much simpler solution, your code could work
directly with the ContextManager
class. This would be a less than ideal
solution, and would very much start to encroach into the realm of the service locator pattern, but that's not
that bad for this particular use case.
Final Words
As I sit here in the 26°C heat of the British summer, with the awful humidity that comes with it, I realise that I've been sat writing this article for a long time, and it's much longer than I had originally intended. So, I'll attempt to wrap it up now.
I hope that you've found this article useful, and that you understand not only the pattern I'm defining, but the benefit of having a definition for it. I think this pattern can be useful to help us approach things in a slightly different way. For example, a lot of web application frameworks implement this pattern multiple times, one for each context type, and I honestly believe that's because we treat those as completely distinct, without any connections what so ever. As you can see from my example above, that doesn't have to be the case.
To the best of my knowledge, and I've researched it a lot, there is no formal definition or pattern that matches what I have attempted to cover and define here. I have tried to give it a name that makes sense, and that allows is to reference it in the future (I for one will be adding it to the Sprout documentation).
Creating this pattern is something that caused a bit of a crisis of confidence, and I've been sitting on it for a while. That being said, I'm really happy with how it turned out, and I would absolutely love to hear any thoughts or feedback, whether that's positive or negative.