Reactive in practice, Unit 2: Prototype the UI and UI integration patterns
Validate what you learned from event storming
In the previous unit, we developed a domain model for the stock trader domain using event storming and domain-driven design. This process allowed us to work with business domain experts and to arrive at a common language and understanding of the business domain in a technology independent way.
In this unit, we will begin to translate the stock trader domain model into software, beginning with the API for our new system.
In order to arrive at an accurate API, it’s common for teams to begin by collaborating with designers and mocking up user interface prototypes. We want to keep this series as close to possible to a real-world project from inception to delivery, so we will also prototype interfaces for Reactive Stock Trader. Interface prototyping is an effective way to validate the learnings from an event storming workshop. Prototyping helps to challenge assumptions and arrive at an accurate final product through peer review, completely through the lens of our users. No matter how perfect our event storming session goes, changes are often uncovered during prototyping!
After prototyping is complete, we’ll introduce the high-level architecture of Reactive Stock Trader based on the output of our prototypes. We will cover a few common and important architectural pattern in microservices, including the Backends for Frontends pattern, and an overview of microservices in general.
User interface overview
Prototyping user interfaces after an event storming session helps to ensure that our implementation is user-focused. Important topics such as resilience, latency, and other non-functional requirements often lead to better products when coming from the perspective of our users rather than internal stakeholders.
First, let’s revisit our domain model in order to align our prototype on the following bounded contexts.
Reactive Stock Trader is made up of three bounded contexts:
- The portfolio bounded context encapsulates all functionality related to the ownership of equities and cash
- The brokerage bounded context encapsulates all functionality related to equity-related transactions, such as buying and selling stocks
- The wire transfer bounded context encapsulates all functionality related to transferring cash in and out of a portfolio
For each bounded context, we’ll describe each possible user interaction, and show a mock user interface that captures a comprehensive set of actions. The prototypes will lead us to a fairly accurate API specification, which we will cover at the end of this unit.
As we work through prototyping, think about what context was lacking from event storming. Specifically, picture the data required for each command and event. Prototyping will help to flush out more definition of data models and will become a key outcome of this unit.
Portfolio bounded context
At the core of Reactive Stock Trader is the concept of a Portfolio, which will contain equities and cash.
Create a portfolio
The first interaction a user will have with Reactive Stock Trader is to create a new portfolio. Once created and accessed, a user will be able to transfer in cash and begin trading.
It’s important to note that for the sake of simplicity we will not be adding user accounts, so each portfolio will be available to load without logging in.
Load a portfolio
Assuming a portfolio has already been created that someone would like to continue working with, we will provide them with the ability to load a portfolio by ID or search for a portfolio by name.
Searching by name will show a drop-down list of portfolios that can be selected. Searching by ID will not autocomplete and will succeed or fail only after the “lookup” button is clicked.
We’ll need to present a high-level view of a portfolio’s performance in terms of return in USD and rate of return as a percentage.
We’ll also show the portfolio’s nickname, which will come in handy if a user wants to create a number of portfolios that captures different investment strategies.
A key part of a portfolio is how much cash is on hand to execute trades, which we will also showcase here. It’s important to note that for the purposes of Reactive Stock Trader, an unlimited amount of USD can be transferred into the portfolio. (If only real life worked like that!)
The core of Reactive Stock Trader is to assemble a collection of equities and keep track of their performance individually. We’ll need to provide a detailed, real-time listing of our current holdings.
Many of the real-time updates will come from integration with IEX, while specific portfolio details (such as number of shares per equity, current portfolio value, and so on) will come from the Reactive Stock Trader system itself. This distinction will become more clear once we explore the API in more detail.
To compliment the functionality we’ve already outlined, we’ll also introduce a very simple quote feature. Searching for a ticker symbol will result in a detailed overview of a specific company that updates in near real-time.
Broker bounded context
Next, we will introduce the functions of the broker, all of which are related to transactions: placing orders, viewing and cancelling pending orders, and viewing a complete transaction history.
An order is not a trade, it’s simply an intent for a trade to happen at some point in the future. In the case of a market order, the trade will happen almost immediately. We’re going to model a few other order types that will showcase the reactive nature of our new system, namely order types such as limit, stop, and stop-limit.
Before an order is executed, it will be in the pending state and displayed on a list of pending orders. This will give users the opportunity to cancel a pending order that has not executed yet.
After an order is executed, it will be considered completed (or executed) and added to a list of completed transactions. Each completed transaction is an event that directly impacts the current value of our portfolio and all of our holdings.
Wire transfer bounded context
To get cash in and out of portfolios, we’ve modelled a wire transfer bounded context that handles all cash wires. In the real world, the underlying wire transfer system may be a completely separate department within a bank, or a separate company with a public API, such as PayPal.
The core functionality of the wire transfer interface will be to move money in and out of accounts. In Reactive Stock Trader, the USD available to transfer in will largely be unlimited for the purposes of demonstrating functionality.
We’ll also have pending transfers and completed transfers, with very similar interfaces to pending orders and completed orders, so we won’t duplicate the interface prototypes.
In our consulting practice at RedElastic, we approach projects like Reactive Stock Trader from a user-first perspective to ensure that a final product is aligned with an ideal user experience. This helps guide us towards the external interfaces — the APIs — of the services we will require in order to enable the desired user experience. Prototyping will also help to guide non-functional requirements, such as latency and uptime requirements, grounded from a user’s perspective.
A common mistake we encounter are teams focusing so much on technical solutioning that they approach user experience as a simple design exercise that can be bolted onto a mostly constructed system. In reality, we need to think about users from the very beginning. Reactive systems are all about the experience our technology brings to our customers.
We should now have a solid idea of what we want to build. That’s not to say that we won’t regularly revisit the scope of the project and our assumptions; it simply means that we have enough thinking accomplished to start planning out the architecture and implementation details with more fidelity and accuracy.
The good news is that, because we used event storming as the cornerstone of our modelling workflow, a variety of stakeholders were involved, from subject matter experts, to developers, to designers. We now have a very clear scope to work within as we decide on the technical details of our system architecture.
The next step is to think about how to connect our UI with the services that it will require.
Technologies and assumptions
Before we dive into the details of a preliminary REST API, let’s begin with an overview of the frameworks that we will use to build Reactive Stock Trader. All of these technologies will be covered in detail throughout this series.
Play is an open-source framework for building incredibly fast web applications using Java or Scala. Play will serve to implement the Backend for Frontend pattern, providing a public API that connects our user interface to our reactive microservices.
Lagom is a framework designed for building reactive microservices, which embraces both the language of domain-driven design and the principles of reactive systems. Lagom is the core of our system, which we will use to express our domain model in a reactive, event-driven fashion from top to bottom.
At the highest-level of integration, we will implement the following components:
- A user interface using Vue.js
- A Backend for Frontend (BFF) using Play
- A reactive microservices backend using Lagom
We’ll now cover a preliminary integration between our three major architectural components, starting with an explanation of UI integration with Play, based on the Backend for Frontend pattern.
Backend for Frontend layer
Our objective is to wire together the UI with a preliminary set of services that can provide the basic functionality required by our prototype interface. In order to make this as easy as possible, we will introduce a key pattern in modern systems architecture: the Backends for Frontends pattern.
The BFF pattern helps to enable a parallel style of development, giving front-end developers more control over the core APIs that they need to integrate with. The BFF pattern also provides flexibility for backend developers as they can move independently with as few points of friction as possible, not forced to directly conform to the rapid changes often required by user interfaces.
Imagine that we have a web interface, iPhone interface, and Android interface. Each one of these interfaces may have small deltas between their functionality, or perhaps completely different functionality altogether. With the BFF pattern, each interface type would have its own BFF responsible for the composition and aggregation of data, which will be perfectly suited towards the functionality provided by that device.
The BFF pattern also prevents us from having to regression test all devices when a change is required only by one. Imagine that we want to add a small piece of functionality on our iPhone interface. With only a single API for all devices, any change to the API made for one device (such as the iPhone app) may break existing functionality in the others (including web and Android apps). Leveraging the BFF pattern means that user interface and user experience teams can move in parallel without worrying about impacting the functionality of other devices. It also means that deployments and project management don’t need to be coupled together across all device teams.
In order to get started, we first need to define the API endpoints. We’ve created a domain model in Unit 1, and a UI prototype earlier in this unit. We’re now ready to get more specific about the endpoints that our Reactive Stock Trader web UI will require.
We’ve used the BFF layer in Reactive Stock Trader, but a perfectly valid alternative is to use an API Gateway or Proxy pattern. The BFF is an alternate take on those options, which is common on projects with multiple UI/UX teams delivering multiple interfaces for the same set of services. Regardness of the approach you choose, ensure that clients are not able to directly access your microservices layer.
The two main ways that services interact with each other in Lagom are service calls for request-response (synchronous interaction) and the message broker API for pub-sub style (asynchronous) interactions.
In the next unit, we will begin a deep dive into creating our service interfaces. In the subsequent unit, we will dive deep into Lagom, along with continuing to expand on the architectural patterns we’ve introduced so far.
Summary & next steps
We have introduced a few important patterns that are of high value when developing reactive systems, namely the importance of prototyping a domain model in a more interactive context, such as a user interface. Prototyping is an essential step to validate the interpretation of our models. This advice is based upon our experience training teams on these techniques for productivity on real-world projects.
Another takeaway is the importance of involving diverse groups of stakeholders in event storming. For example, if the user interface prototype is wildly different than the expectations of the design and UX teams, were they involved enough in the event storming process? We’ve encountered instances of development teams using event storming in relative isolation, then becoming disenfranchised when the output is not approved by business stakeholders. This may happen for a variety of reasons, but by far the most common is a lack of involvement by non-technical stakeholders, such as designers, project owners, project managers, executives, and clients. It can sometimes be difficult to pique the interest of non-technical stakeholders in event storming to begin with. A helpful way to position event storming is that conversational flow will still exist, just like any other planning meeting, but there is living, tangible output in the form of models. This can be intimidating in organizations that are accustomed to long, meandering, free-flowing meetings with no output. But we’ve witnessed event storming being something of a catalyst of cultural change within an organization towards a more productive and collaborative style.
In the next unit, we’ll dive deep into the Java implementation details of commands, events, and queries. These examples will be fully grounded in our business domain and the business language we defined during modeling. As we move into coding with such a clear picture of requirements, we believe the power of event storming and domain-driven design will be demonstrated with clarity, and will show how the two techniques combined can increase the comprehension of code and align it to the language of the business domain.