Composable Architecture
Introduction
This document will serve as a brief explanation about what Composable Architecture is and how it's applied to our own product. We'll use our own Applications as examples.
What is it?
Composable Architecture is a design pattern in which developers create reusable components in order to build applications more quickly and easily.
It's basically the combination of small software systems that can work independently but can also be combined and form a more "complete" system. These modules work fine by themselves, but can be complimented and integrated by and with others.
Why do we use it?
- Allows for the separation of code and specialization of the developers by module.
- Allows Constellation to have a subscription based service. This brings the customization needed to adapt to all type of clients, allowing them to combine the modules they need and put them together however they like, in order to have the most suitable solution for their business.
- Optimizes scaling. For example there is an excessive ammount of Users flooding the People module, it's possible to only scale that module, without having to do so with the entire system. In the end this also means a faster solution to end Users and offers us a better way to manage costs.
How does this apply to our own App?
Crud Operations - Basic Example
Let's take a look at the Travel Request module for example. If this is the only subscription the Tenant possesses, then they can still add Travels/Trips. You'll just need to add a Name to the travel and, in the case of Trips, you add a Description and Users as Participants.
If this is not the case and the Tenant is subscribed to more modules (People, for example), they'll be able to add Teams and/or Employees as participants, along with the Users.
Approval Flows - Complex Example
Now take a look at our Time Planning module. Along with registering their time, users are also able to submit these timesheets for approval. In order to make this work, first we need to determine who the approver is. So if one Tenant only subscribes the Time Planning they'll be able to define these approvers, simply by adding them to the Approver list on the Configuration tab.
In case this Tenant decides to complement their subscription with the Entities Manager module, besides having the base approver behavior with Users, they also have the possibility of approving Timesheets by Cost Center. In other words, they can make certain Users approvers of specific Cost Centers, and they'll only approve Timesheets associated with their defined Cost Centers.
Technical Approach
How do we reflect this in our code?
First, it's important to ensure that the information domain is well defined. Meaning that there shouldn't be any required fields that fetch information from other applications. Every required field has to part of the information domain that you're currenly building. Everything else is treated as complementary information.
Another important point is that you make sure these modules can only be accessed by people who are subscribed them.
Take this as an example: You want to associate Cost Centers to a certain trip. To do so first, you need to be subscribed to Entities Manager.
So to validate if you have this subscription you'll need to add the OryonSource to the TripModel CostCenters property like the following example.
[OryonSource(typeof(OryonGenericConnectors), nameof(OryonGenericConnectors.CostCenterSearch))]
public List<GenericLocalizedReference> CostCenters { get; set; } = new();
There are a few things to break down here, so let's start with the OryonGenericConnectors class.
- Its responsible for aggregating all existing Connectors.
- Abstracted in the Oryon.Constellation.Shared project to make sure we have these Connectors centralized and available to use in every project.
📝 NOTE: In case you're unable to locate the file here's the namespace: Oryon.Constellation.Shared.Analytics
Here's an example of what a connector looks like:
public static readonly OperationAllowedRequest CostCenterSearch = new(
assembly: OryonAssemblies.EntitiesManagerApi,
@namespace:${OryonAssemblies.EntitiesManagerApi}.Controllers.CostCenter,
controller: "CostCenterController",
action: OryonGenericActions.SearchAsync,
verb: OryonVerbConstants.POST);
📝 If the Connector you need is missing you'll need to add it to the the class above. Follow the same structure and be extra careful with the namespace and controller name. If any of these properties do not match the original controller then this will not work as intended.
So, in this case, the OryonSource annotation would determine if the User is in a Tenant that has the subscription to the Entities Manager module. If they are, then this Cost Center selection would be available to use. If not, then the Cost Center field would be blocked and they would see a message informing them that they're missing the subscription needed to use it.
Conclusion
This guide offers a brief explanation on what a Composable Architecture is and how we implemented it in our solutions.