Overview
Arena Financial Services were looking for highly-skilled software developers who could quickly build from the ground up a financial platform for small businesses in need of a payments system that could handle their day-to-day operations.
The main objective of the project was to develop a payments platform that could handle the needs of small businesses while providing robust and technically sound architecture. The main challenge was to architect future proof and solid software foundation and iteratively deliver business-critical features on top of this abstraction. And all of these while staying within the limited budget of a startup.
Throughout the development process, the ST6 team focused on getting the fundamental payments architecture right and then delivering features on top of this foundation that would provide immediate value to Arena Pay customers. Those include an intuitive user interface and easy-to-use payment features via 3rd party integration with services like Plaid, Stripe, QuickBooks and, CheckIssuing. The developed system also leverages software design best practices for financial platforms and is compliant with industry standards to ensure customer data is safe and secure.
The Solution
There are a couple of things that you better get right the first time when you are engineering a financial platform. Before taking a close look at each one of them, let's check out the high-level architecture of the platform.
The diagram below illustrates how all components of the system interact with each other.
Let's drill down and examine each layer.
Data Modeling
The way you architect your data model is the most critical part of any (financial) system. There are many proven ways to do it, but from past ST6 experience, we believe that the best way is to use event sourcing.
Event sourcing is a design pattern used in software engineering that involves storing the changes made to an application's state as a sequence of events. You can think of it as an immutable ledger that is append-only. Each event (often referred to as a Domain Event) represents a specific action, such as a payment or a refund, and includes all the relevant data for that action.
This technique has several advantages:
- It provides a complete audit trail of all transactions, which is essential for compliance and regulatory purposes.
- It enables the system to replay events in case of errors or discrepancies.
- It allows for flexible querying and reporting capabilities, as data can be aggregated and analyzed based on specific events or timeframes.
However, the main advantage of event sourcing is that it enables accurate representations of real-world systems by efficiently modeling how they operate over time. As time progresses in only one direction, events are appended to the event log in chronological order, forming a complete and immutable record of all the changes that have occurred in the system. This allows modeling initially unforeseen events like rollbacks, compensating actions, and error recovery more effectively since the event log contains a complete history of the system's behavior and can be used to understand the reasons behind any unexpected outcomes.
To implement event sourcing, the ST6 team used MongoDB as the database, which allowed for fast and efficient storage and retrieval of event data. The MongoDB Atlas cloud offering was chosen because it allowed peering to the AWS VPC cluster of Arena enabling a private and secure connection between the database and the application server.
ST6 team also employed Domain-Driven Design (DDD) principles to ensure that the domain events accurately reflected the business domain and were easy to reason about.
Domain-Driven Design
To handle the complexities of a financial platform the ST6 team decided to use Domain-Driven Design (DDD). DDD is a software development practice that emphasizes the importance of understanding the business domain in which the software operates. It's an approach that helps teams focus on the core business logic and make sure that the software aligns with the business requirements.
There are many important aspects when you are starting a strategic Domain-Driven Design, but the two most critical ones are the establishment of a common vocabulary (known as ubiquitous language) and the discovery of the so-called bounded contexts.
Ubiquitous Language
The term "Ubiquitous Language" comes from the seminal book Domain-Driven Design by Eric Evans. It describes the practice of establishing a common vocabulary and language by everyone involved in the project including stakeholders, domain experts, product managers and developers. This language should be used in all conversations, documentation, customer interviews, support tickets, etc.
The key aspect of having a shared vocabulary is the natural removal of any ambiguity that might come from using different terms to describe the same parts of the system. We should also note that this language is not written in stone but is a living one and should be evolved along with the evolution of the domain.
For Arena Pay, we have identified users, accounts, vendors, payments, assets, and bank accounts as part of the core domain vocabulary.
Bounded Context
A bounded context is a logical boundary between different parts of the domain. It helps to ensure that a domain model is consistent and well-defined within a specific context, even if different models or concepts are used in other parts of the system. It enables teams to work more effectively, with a shared understanding of the domain language and concepts used in a specific area of the system.
For example, in the case of Arena Pay, we initially identified the following bounded contexts:
- Account Management
- Vendor Payment
- Asset Management
Let's take a closer look at each one of these.
Account Management
This bounded context deals with account and user entities. To quickly bootstrap the authentication and authorization parts of the system, we decided to use Auth0. It provides a lot of this infrastructure out of the box, including a management portal, social login integration, and pre-built React components.
An important domain decision was made early in the product development that a given user can be part of multiple accounts. The proposal was made by the development team and the product owners quickly saw the business value of this feature. For example, a financial advisor could be added to multiple accounts to guide a handful of Arena Pay customers.
Vendor Payment
The Vendor Payment bounded context is the heart of the Arena Pay platform, as it handles the processing of payments between small businesses and their vendors. It is responsible for creating, updating, and canceling payments to vendors as well as registering bank accounts and credit lines.
This bounded context emits the core domain events related to payments that other parts of the system can act upon. For example, when a payment is created, an integration event is asynchronously dispatched and a 3rd party integration can handle it. In the case of Arena, the payment is processed with CheckIssuing via its API. CheckIssuing integration also defines its bounded context that handles and dispatches various payment-related events.
Asset Management
The Asset Management bounded context encapsulates all digital assets that can be attached to several events in the system. Those assets can be invoices, receipts, or any other digital document. The context encapsulates the mechanics for uploading, viewing, and downloading documents, as well as versioning and auditing capabilities.
The ST6 team chose industry-leading Amazon S3 for asset storage which provides best-of-class security and performance. All files were stored encrypted in a private S3 bucket and were accessible only via a pre-signed URL.
Separating reads and writes for the data store
Another key aspect of the Domain-Driven Design was the application of the Command and Query Responsibility Segregation (CQRS) design pattern. CQRS is a pattern that separates the responsibility of handling commands (which update the state of the system) from the responsibility of handling queries (which read the state of the system). This separation allows for different models to be used for reads and writes, which can improve scalability, performance, and maintainability.
In the case of Arena Pay, the ST6 team used CQRS to separate the read and write models for all bounded contexts. Each bounded context will define its own set of commands (write model) and queries (read model). The write model consists of the event-sourced domain events and is responsible for handling all commands in the system (such as creating, updating, and canceling payments, adding a bank account, registering users, etc.). The read model comprises a collection of MongoDB views (also known as projections) that provide optimized representations of the queried data for different use cases, such as displaying payment history to users or generating reports for accounting purposes.
GraphQL API
To provide a unified API umbrella on top of the multiple bounded contexts we decided to use GraphQL. Apart from providing a single API, it allows clients to retrieve and modify data from multiple bounded contexts in a single request. This approach improves the overall performance of the system by reducing the number of requests made to the server and reducing the amount of data transferred between the client and the server.
The ST6 team chose to implement the API using the Apollo GraphQL platform. Apollo provides a unique set of features tailored for enterprise use cases including schema federation. In the case of the Arena Pay platform, each bounded context federated its schema with queries and mutations, where queries mapped to CSQR's queries and mutations mapped to commands. Each bounded context schema was combined in a unified supergraph that exposes all the functionality of the financial platform.
Front end
For the front end, the ST6 team choose the battle-tested React framework with Semantic UI as a component library. The business look and feel of Semantic UI was a perfect fit for a financial platform and the theming capability allowed us to match the UI system designed for the product.
Having GraphQL as an API layer allowed for a more flexible front-end composition where queries and mutations were co-located next to the components that were using them. This made it easy to reason about the data flow and keep the codebase organized. The Apollo Client library was used to handle GraphQL queries and mutations in the front end.
Here is a quick demo of the platform done live at Finovate 2022 by Arena's CEO Heather Tuason:
Conclusion
Building a financial platform for small businesses is no easy feat, but the ST6 team was up to the challenge. By using event sourcing, Domain-Driven Design, and Command and Query Responsibility Segregation, we were able to create a robust and scalable platform that met the needs of Arena Financial Services and its customers.
The resulting agile architecture allows for fast iterations and quick changes to requirements while the domain is being explored. This flexibility however is not in exchange for a sacrifice in reliability expected for a financial system.
Arena Pay is a prime example of how modern software development practices can be applied to solve complex business problems. It provides small businesses with a comprehensive payments platform that can handle their day-to-day operations, while also providing a secure and compliant environment that meets industry standards.