XOOM Designer
The visual model designer for DOMA, DDD, and compressed Ports and Adapters architecture providing low-code project delivery for the VLINGO XOOM platform.
Introduction
Welcome to the VLINGO XOOM Designer!
You are in the right place if you are interested in accelerating your learning and application of Domain-Driven Design, Microservices Architecture, as well as Reactive Architecture and Reactive Programming. These approaches have proven to be a strong foundation for building modern services, applications, and systems that are robust, modularized, scalable, and that use modern architectures.
Accelerate your learning and use of DDD and Microservices with XOOM Designer.
To accelerate learning and the use of these approaches to software development you need some very direct instruction and to employ opinionated tooling. That's why we provide the XOOM Designer and the overall XOOM platform SDK.
Our bottom line: We want you to succeed!
The VLINGO XOOM platform was created to help you and your fellow software developers to confidently move forward and modernize their skills and the systems that are developed by you and your teams. With the XOOM Designer you can greatly accelerate development of business modernization efforts. Just look at what you can do with XOOM Designer:
Use visual modeling to define your services, applications, and systems
Create a compressed Ports and Adapters (i.e. Clean) Architecture that includes
REST API
Domain Model
Persistence
Messaging
Container Configurations (native, Docker, and Kubernetes)
You get DOMA and DDD patterns support with no additional effort
Generate and immediately build and run your design
Design and Run Your Services and Applications Within Minutes
You are learning some advanced software development approaches, techniques, and architectures and you have limited time. Consider the potential of applying the following rapidly, rather than taking months or years to absorb all the details:
Domain-Driven Design (DDD)
Domain-Oriented Microservices Architecture (DOMA)
Microservices Architecture
Reactive Architecture and Programming
If you or your team face a steep learning curve, don't give up and fall back to familiar, yet outdated, frameworks and tools. Let XOOM Designer help.
The VLINGO XOOM platform was created to help you and your fellow software developers who face such challenges to confidently move forward and modernize their skills and the systems that are developed by you and your teams. One platform component that greatly accelerates developer modernization efforts is the XOOM Designer. Our Designer supports visual model definition, along with a compressed Ports and Adapters architecture that includes REST API, persistence, and container definitions. Following design, your DOMA and DDD project is generated and immediately built. Your applications and microservices can be running within minutes.
Here's how simple it is to create a new Bounded Context with Reactive, Event-Driven, and Microservice Architectures:
Select a Programming Language and Platform (currently Java only)
Define your top-level Bounded Context name and module
Visually design Aggregates, even with Value Objects, collections, and Domain Events
Choose persistence type options, including Event Sourcing, Key-Value, and CQRS
Specify how your Bounded Context will be deployed
Select code generation options and generate your entire architecture and domain model
With instantly executable microservices and applications, you and your teams are in a position to quickly implement custom business logic within the pre-generated model as you take over and employ a full software development life-cycle. The XOOM Designer can be used to rapidly generate alternative models so teams can experiment with DOMA and DDD results. This is also a great way to learn Microservices and Reactive Architecture along with DOMA and DDD. You will learn both DDD strategic and tactical modeling as they are powered by an actor-based ecosystem.
XOOM Designer Usage Options
Before you can design a model and surrounding architecture with the XOOM Designer, you must gain access to it. The following explains both cloud and local access. There are currently three ways to run XOOM Designer, each is discussed exclusively from the others.
XOOM Designer now integrates with XOOM Schemata making event and other schema types easier to create and distribute to dependents. You must run Schemata along with XOOM Designer to support the integration.
There is a docker-compose.yml
that can be used to make starting both tools together. Yet, Docker will start Schemata on your local development computer. If you prefer to run Schemata on another computer, such as a team or organizational server, there is no need to start Schemata locally.
See below for custom configuration of Schemata access.
Quick Start
The quickest way to start XOOM Designer is to run it on Docker. Taking advantage of the docker-compose
file created by the VLINGO XOOM team, you can initialize Designer simply using these two commands:
Then, XOOM Designer can be accessed at http://localhost:19090/context. When you start XOOM Designer from docker-compose
, XOOM Schemata also will be started and initialized, and can be accessed at http://localhost:9019. Learn more about the XOOM Designer/Schemata integration here.
XOOM Schemata Integration
XOOM Designer integrates directly with XOOM Schemata to define both producer and consumer schema references. We recommend that you use the Docker image for XOOM Schemata. The quickest way to do so is to use the docker image published by the VLINGO XOOM Team:
TIP: When using Docker to run XOOM Designer integrated with XOOM Schemata, name your Schemata container with --name xoom-schemata
so that you can reference it in Designer-Schemata integration options:
Select the Designer menu OPTIONS, and use the image name as the host.
Using the XOOM Designer on the Cloud
[This section will be provided soon.]
Using a Locally Installed XOOM Designer
To use the Designer on your own development computer requires an installation step. The installation process is short. Before you start, just check if you have these tools already installed:
Java 8+
Maven 3.8.1
Download the XOOM Designer compressed distribution file using curl
.
Note that the version examples that follow show a version that is not necessarily current.
File type: zip
zip
File type: tar
tar
Extract the file content, then set an environment variable named VLINGO_XOOM_DESIGNER_HOME
indicating the absolute path for the uncompressed folder. Additionally, on Unix-based operating systems, it is necessary to enabled read and execute access on executable script as following:
Ensure it's all set by verifying the version:
The XOOM Designer provides a web/graphical user interface for a rapid application generation. Simply open a terminal window and run the Designer.
Following this your preferred browser will open with a wizard-fashioned screen, consisting of five steps.
If you would like to contribute development efforts for the XOOM Designer, there are specific instructions available in the project README.
The next section shows you how to model with the XOOM Designer. Let's get started!
Modeling With the XOOM Designer
There are only a few primary steps required to create a new subsystem solution with the XOOM Designer. Remember this this tool provides an opinionated approach to Microservices and Reactive Architectures, which makes it very straightforward to delivery bug-free solutions in record time.
In the examples that follow, the source code shown is that generated by the XOOM Designer based on the model information provided by the developer. You will not (currently) see the source code during design mode.
Opinionated Software Modeling
The VLINGO XOOM team considers EventStorming to be a vital activity in the exploration and learning within a software system. Therefore, we have made the XOOM Designer work from the results of EventStorming sessions.
This requires that the EventStorming sessions produce not only a big-picture model but also a design-level model. The design-level model includes the following:
Command messages that are to be sent to an instance of an Aggregate type
The Aggregate types that receive command messages and handle them by performing business rules, emit events, and transitioning the Aggregate's state from previous to new
Events that are emitted by the Aggregate command handlers
This is depicted in the following EventStorming model:
The EventStorming model indicates that the following model collaborations and outcomes happen:
The
Register
command message is sent to theInvestor
Aggregate, which is a new instance and is initialized by handling theRegister
messageThe
Investor
runs any business rules, validates the command, and applies the newInvestorRegistered
eventAn external subsystem, in this case the Account (Bounded) Context, publishes the
AccountClosed
event which is consumed by the Investor Context (the current model context under design)The incoming event is translated to a
Suspend
command that is sent as a command message to theInvestor
The
Investor
accepts theSuspend
command message, validates it, and applies the newInvestorSuspended
event
We prepare this EventStorming result for use in our DDD-based model for the Investor Context, which is the modeling project that will be created as an example herein. This preparation is done by stacking the EventStorming elements as they are used in the context. This is seen in the following figure:
The Investor
Aggregate is now the focus of this part of the model. The Investor
first receives an initializing Register
command message and emit the InvestorRegistered
event. Following this, at some point the AccountClosed event is received from the external Accounts Context, translated to the Suspend
command message, and dispatched to the Investor
Aggregate. The Investor
subsequently validates the Suspend
command message and causes the InvestorSuspended
event to be emitted.
The Aggregate types are arranged for view within the XOOM Designer quite similarly as the Investor
model element stack in the previous figure. Thus, the slices through each subsystem, or Bounded Context, are appropriately visualized. This is shown in the following figure:
Corresponding to the above EventStorming model design, we consider that the basic pattern of design should be based on slices through the architecture. We find this to be a clear way to implement use cases. This is how we see an architectural slice:
A REST request arrives a resource request handler registered with XOOM HTTP
The REST request handler adapts the incoming request to data that can be consumed by the inner domain model and dispatches to an Aggregate by sending it a command message
The Aggregate receives the command message, executes and validating domain logic, and assuming the command is valid and accepted, applies a new event and/or state
The application of the new event and/or state results in the atomic, transactional persistence of the model elements, and sends a confirmation message to the Aggregate
When the persistence confirmation is received by the underlying Aggregate base class implementation, the concrete Aggregate is informed to mutate its state accordingly
Simultaneously with step 5, the persistent storage mechanism dispatches the event and/or state to projections that are responsible for creating and updating queryable views and publishing events to a producer exchange
The following are the principles behind the XOOM Designer software model tool.
Currently you may model for the Java platform or the .NET platform (available soon).
A domain model is the primary focus of the modeling, and the model is designed using DDD tactical modeling tools.
All architectural decisions are made to support the domain model, and the architectural mechanisms include the application of the DDD Context Mapping patterns.
There are six total modeling steps: Platform (available soon), Context, Aggregates, Persistence, Deployment, and Generation.
The individual steps follow.
Step 1: Platform
Currently there is no Platform selection step because only the Java platform is available at this time. Soon there will be a Platform selection step. This is the current Platform step.
Platform Choices
Language Choices; Select One
Current Availability
JVM
Java or Kotlin
JVM with Java available now; Kotlin available soon
.NET
C# or F#
.NET with C# available soon
Kotlin is already supported as a JVM language. XOOM Designer with Kotlin code generation support is under development.
.NET with C# support will be provided first, followed by F# support.
The Context step is next (and currently the first step).
Step 2: Context
The Context step defines the Context name with project artifact and service packaging, an opinionated step.
A Context is provided as a DDD Bounded Context with a Ubiquitous Language. The Ubiquitous Language is expressed as a domain model, such is primarily supported in the Aggregates step.
For the JVM Platform:
Group Id: Enter the Maven
pom.xml
standardgroupId
. An example of such isio.vlingo.brokerage
, which will be inserted in the project'spom.xml
as the following:<groupdId>io.vlingo.brokerage</groupId>
Artifact Id: Enter the Maven
pom.xml
standardartifactId
. An example of such isinvestor
, which will be inserted in the project'spom.xml
as the following:<artifactId>investor</artifactId>
. This also serves as the name of the Bounded Context, such as Investor Context.Artifact Version: Enter the Maven
pom.xml
standard artifactversion
, which is a semantic version. An example of such is1.0.0
, which will be inserted in the project'spom.xml
as the following:<version>1.0.0</version>
.Base Package Name: Enter the base package name that will serve as the prefix for all Java packages. An example of such is
io.vlingo.brokerage.investor
, which will be used to derive all other package names in the generated project. The example project would have:io.vlingo.brokerage.investor.infrastructure
andio.vlingo.brokerage.investor.model
, which serve as the two major layers of the Compressed Ports and Adapters Architecture.
The following is an example of the Context page.
Currently Maven build is supported. You may easily convert the generated project build to Gradle by using the Gradle conversion task.
Step 3: Aggregates
The third step designs the feature-based slices through the architecture, another opinionated step.
In DDD, the Aggregate is an important tactical pattern. It represents a transactional boundary around a major domain model concept.
The tool presents an op inion that Aggregates accept command messages, transition state, and emit events.
The state object type is named with the Aggregate type name with the word
State
appended. For example, the Aggregate named Investor has a state type namedInvestorState
.Command messages are sent to an Aggregate to transition its current state to a new state, and to emit a Domain Event that captures this happening in the model.
The user interface for declaring Aggregate types is shown in the next figure.
Click the + NEW AGGREGATE
button to open the Aggregate dialog box. This is where you declare the details about an Aggregate type to be included in your domain model. When the dialog displays, fill in the appropriate sections of the dialog.
There are a number of sections in which to enter model type design information.
Aggregate Name
Every Aggregate type has a unique name, which serves as the type name of the Root Entity protocol. A protocol is the way that clients send messages to the Aggregate, which is generally know as an interface in Java and C#. This is the name not only of the Root Entity type, but represents the overall Aggregate concept. As a convention, if the Aggregate protocol/interface has the name Investor
, the implementing class is named InvestorEntity
.
State Fields
The state of the Aggregate is prefixed with the name of the Aggregate type's protocol/interface followed by the word State
. If the protocol name is Investor
then the state type name is InvestorState
. The state type is the only value directly held by the Root Entity. The fields of the state type are also known as attributes or properties. Each field has a name and a type. In the above example the Investor
type has three declared state fields, one named name
of typeFullName
, one named suspended
of type boolean
, and another named balanceSheet
of type BalanceSheet
.
Any state field type can be declared as one of two collection types, List<T>
or Set<T>
, or it can be left a "bare" type, which means without a collection.
By convention the state type is immutable. Transitioning from one state to the next is accomplished by full state object replacement. That is, the methods on the state type are functions; that is, side-effect-free behavior. This means that every state business behavior method returns a new state instance that contains the pre-existing field values merged with all new field values.
Reminder: In the examples that follow, the source code shown is that generated by the XOOM Designer based on the model information provided by the developer. You will not (currently) see the source code during design mode.
For example, assume that the name
of an Investor
is Zoe Doe, and when Zoe gets married, her name changes to Zoe Jones-Doe. The InvestorState
would contain the same first/given name of Zoe, but the family/sir name of Doe would be replaced with Jones-Doe.
The Aggregate accepts and handles the changeName(FullName)
command message, which causes the state to transition.
The state can have any appropriate number of fields. For example, if an Investor
could be suspended from trading, there could be a suspended
field of type boolean
in addition to the name
.
The FullName
type is a Value Object, which can be defined by clicking the button named + NEW VALUE OBJECT
that sits just below the Aggregate Name field. You define the Value Object by giving it a type name and some number of fields/attributes. For example, FullName
has the following definition:
The following table shows the Value Object types defined for an used in the Investor Contexts.
Value Object Type
Field Name
Field Type
BalanceSheet
transactions
List<Transaction>
BuyDetails
symbol
String
price
Money
shares
int
FullName
first
String
last
String
Money
value
String
Transaction
datetime
DateTime
amount
Money
type
TransactionType
TransactionType
value
int
After defining Value Object types you can edit any one of them using the Value Object dialog box by clicking theEDIT VALUE OBJECT
button.
You probably noticed the field named balanceSheet
of type List<Money>
, and may have wondered if it really belongs as a member of the Investor
. Let's say initially it seemed like a good idea, but then later it seemed much less desirable. It likely belongs on an Account
, which isn't even in our Investor Context.
Okay, great, but we must admit that it was included only to show off the ability to declare a List<T>
or Set<T>
of any type, such as List<Money>
.
So, yes, factor out the balanceSheet
from the Investor
type.
Next up: Declare the Domain Events that can be emitted from the Aggregate in reaction to command message handling.
Events
Our opinion is that an Aggregate likely emits Domain Events in response to handling command messages that it receives. This section is where you declare the Domain Events that the Aggregate will emit under various command stimuli. An event is named as a fact, generally with a noun prefix and a postfix of a verb in past tense. For example, such as fact capturing event name is InvestorRegistered
.
The event is designed to contain one or more specific state fields. The event must always contain the unique identity of the Aggregate, namely the id
of type String
. In the case of InvestorRegistered
, it will contain both the id
and the name
of type FullName
of the Investor
.
When defining zero-parameter message handling methods and self-describing events—those that have no state beyond the unique identity of the Aggregate—there must be some specific handling. Such events are said to be self-describing because the name of the event itself indicates that a specific action has occurred and will/may be taken downstream. They are the result of command messages that require no parameters. For example, suspend()
needs no parameters, and the resulting InvestorSuspended
event need not have any state other than the Investor
unique identity. Still, the event indicates that a state transition has occurred, and ultimately requires setting the suspended
field to true
. Since no state transition is obvious to the Designer and its code generation step, the post generation source code will require some modifications. This is explained in further detail below in this same subsection.
Command Methods
The protocol of the Aggregate type declares the messages that may be sent to an Aggregate object instance. In the XOOM Designer these are opinionated and limited to Command Methods. In brief, you will provide the
Command Method Name, which is the name of the message handler method and also the name of the message that can be sent by a client to the Aggregate instance
Parameters, which are selected among those declared as State Fields, or none
Event, which is selected from among those declared in the Events section
The following figure shows this, and following it more detail is provided.
For the Investor
type, one such message with a corresponding message handler is the method register(FullName name)
.
Enter the message handler method name
register
Select the parameters to the message that the
register()
method accepts drop-down. In this case the only parameter isname
. For this message handler, as explained next, there is no need to select theid
as a parameter.The
register()
method is a creational or factory method because it is used to initialize theInvestor
state. Thus, toggle on the option: Involves creation of entity?
There is a special why to select a parameter that is intended for use with a collection-based state field. There are four basic types supported, as shown in the following table. The example uses the field named credentials
, which has the type Set<Credential>
:
Collection Name & Symbol
Type of Operation
credentials*
Add all elements in the given collection parameter to the state field collection. Example:
void declareAll(List<Credential> credentials)
credentials#
Merge all elements in the collection, if any, with the elements in the given collection parameter. If elements in the given parameter collection already exist in the state's collection, they are replaced. If elements in the given parameter collection do not exist in the state's collection, they are added. Example:
void merge(List<Credential> credentials)
credential+
The parameter is a single element that will be added to the state field collection. Example:
void declare(Credential credential)
credential-
The parameter is a single element, that if existing in the state field collection, will be removed. Example:
void discard(Credential credential)
In actuality, the above add-all
, merge
, add
, remove
are implementation dependent. The actual implementation can be as desired. The idea behind the symbols is to provide standard operations that are common with collections.
It is unnecessary for the unique id
to be passed with this creational register()
message because the unique String id
is always provided with construction. You can see that in the following code that is generated by the XOOM Designer:
As seen in the previous code example, pass only the arguments other than the unique identity that are used to initialize the state.
Since an Aggregate instance always has it's id
available from construction, it would be rare if the id
where passed as a parameter, if ever.
Previously zero-parameter message handlers and self-describing events were discussed and the refactoring currently required to deal with the assumptions made by the Designer code generation. What follows explains how those situations can be handled.
Some messages can be sent with no parameters because the state's transition is implied by the message name. For example, if an Investor
could be suspended from trading, the non-parameter suspend()
message could be sent, which implies that a boolean suspended
field/attribute on the InvestorState
would be transitioned to true
.
Since there are no explicit hints about the state to be transitioned by a command message handler that takes no parameters, the state type has a // TODO
comment generated because the developer must determine and write the code for what the state transition means. As seen above, this involves replacing the // TODO
comment in the suspend()
function with a single line of code. As a reminder, here's a small snippet of code that shows the edits:
Take another look at the InvestorEntity
source used to transition state:
Even before the InvestorSuspended
event is applied (i.e. emitted) the InvestorState
is temporarily transitioned. This is generally always the case because the newly transitioned Aggregate state will be used to provide the fields/attributes to the event. In the case of InvestorSuspended
this is not absolutely necessary because the event requires only the unique identity (i.e. id
) of the Aggregate. The state is permanently (see the below Info box) transitioned by the applyInvestorSuspended()
after the initial apply()
is confirmed.
The Aggregate state might now retain the suspended state for all times forward because the Investor
suspension can be reversed later. The point of "permanently" is that the InvestorSuspended
event will be applied for that moment in time when the Aggregate state is reconstituted. If an event that reverses the suspension occurs later in time, it will also be reapplied during reconstitution for the point in time that happened later.
When an event is applied with the apply...()
method it is persisted asynchronously without the Aggregate's knowledge.
In the case of the Aggregate being a StatefulEntity
subclass as opposed to an EventSourced
subclass, the state can be applied without an event. You determine this by you selection in Step 4: Persistence (see below). The following selection causes all Aggregates to extend StatefulEntity
:
State Store for Key-Value Persistence
This is a choice made instead of the following, which would cause all Aggregates to extend EventSourced
:
Journal for Event Sourcing
Note that when using Event Sourcing, all message handlers must emit an event.
Once the message handler method name is entered and parameters are selected, optionally select an event that the message handler method emits. The choices of event types are from the Events section of the dialog. You can see this illustrated in the previous figure that shows the suspend()
message handler's declaration.
When self-describing events, such as InvestorSuspended
, are used, the Designer has no hints to transition the state on its own. (There will be future attention given to this by tooling, but currently state transition hinting is not supported.) There are two places where this must be cared for.
The state objects function that deals with the specific transition must be refactored. This was just explained above.
Some tests will almost certainly require some minor refactoring to assert state transitions that are non-default values.
Consider some refactoring required for tests:
The default value of
suspended
isfalse
, but the Designer will inevitably generate tests withtrue
, because we consider that the positive state perspective is more common. All test cases that consider the default state astrue
must be adjusted tofalse
instead.It might seem that all cases where the state is transitioned from
false
totrue
require adjustment, but actually the default oftrue
is also used in those cases.The simplest way to find the required refactoring locations is the run the tests, which is built in to the build process. All failed tests will be due to the wrong default state assumption.
All of the above refactoring state will require only a few minutes to complete.
Architecture: API, Producer Exchange, and Consumer Exchange(s)
The vital parts of the architecture responsibilities are specified while defining the domain model. There are three parts to the architecture that are available from within the Aggregates dialog box:
Declare the REST API for incoming requests, such as from browser-based user interfaces and to offer an Open-Host Service for integration with other subsystems
Declare the Producer Exchange (a.k.a. messaging topic) for outgoing events as well as how the events will be registered with XOOM Schemata, which supports providing a Published Language that are provided to consumers
Declare one or more Consumer Exchange points (a.k.a. messaging topic) for incoming commands and/or events, as well as the paths for pulling type-safe event schemas from XOOM Schemata, which supports the consumption of one or more Published Languages
All three of these are derived from the Aggregate currently under model design. The following figure shows the API section and the Producer Exchange section for the Investor type Aggregate:
API: The REST API for a given Aggregate provides a place for a slice through the architecture to begin. The following API parts must be provided:
Root Path: The root or anchor URI path that all detailed REST requests will have, which is often a collection of resources, such as
/investors
.Path: The resource URI path that extends beyond the Root Path. Often a
POST
to a collection will be the same as the Root Path, in which case*
can be used to indicate that Path is the same as Root Path. Otherwise, a fully identified entity URI is required to which a request willPUT
,PATCH
, orDELETE
the resource. This is seen in the second example.HTTP Request Method:
POST
,GET
,PUT
,PATCH
,DELETE
,HEAD
, orOPTIONS
.Aggregate Method: The command message to be sent to and instance of this Aggregate type and the message handler method that will process the command, which is selected from among the Command Methods previously declared.
When the REST request arrives it is translated into a message that is immediately and directly dispatched to the domain model Aggregate instance. There is no need for a layer between the REST request handlers because the domain model requires no special setup or transaction management. That is all coordinated between the request handlers and the Aggregate.
Producer Exchange: This is a publish-subscribe messaging exchange, often called a topic, through which outgoing events will be sent/published as messages. Refer to the following figures for a visual mapping of the concepts expressed next.
These exchange parts must be provided:
Exchange Name: By default, it is assumed and recommended that only one exchange should be used for each generated project. Therefore, this field is always read-only and its value is predefined from the pattern
{projectArtifactId}-exchange
. Depending on the architectural messaging mechanism in use, this might be called a topic name rather than an exchange name. For example, RabbitMQ uses exchanges and Kafka uses topics. Having that in mind, after the project is generated, you can edit the XOOM properties file and, if you find it necessary, change the default exchange name.Schema Registry Path: The following figure shows the UI that prompts for the the
Org:Unit:Context
pattern for the Registry Path. It serves as the prefix path to where the event schemas will be registered in XOOM Schemata. The Org is the name of the organization that owns the schema. The Unit is the business unit or division within the organization. The Context is the name of the fully-qualified package name or namespace of the Bounded Context of which the schemas are a part. Note that the Context will likely be the same used in the Context page for the Base Package Name. This path can be created and selected using the Schemata integration. Click theOrg:Unit:Context
field to use the Schemata UI.Schema Name(s): The one or more domain events declared in the Events section of the Aggregate dialog that are to be pushed into the schema registry. Just because an Aggregate emits a given event doesn't automatically qualify it as required sharing with other subsystems.
This following figure shows the Producer Exchange under design:
This Schema Registry Path is selected through the XOOM Schemata integration, as was previously discussed. If your Organization, Unit, and/or Context is not already defined for this model, you can define them directly through the integration, as follows. The following figures show how the Org:Unit:Context
is created and selected
All of the schemas are pushed during the initial build of the project. Thus, the XOOM Schemata tool must (or should) be running during that time. The event can be pushed in a subsequent build, but they will not be available to for other Bounded Context that depend on them until they are pushed into the schema registry.
Consumer Exchange: This is the section where the Aggregate under design can declare its dependency on an exchange/topic, and the specific schema types that it requires.
The Consumer Exchange shown in the above figure is accounts-topic
. Each schema type to be received on that topic is declared on separate lines, followed by the Aggregate's message handler method that will receive the resulting command. In the case show above, the Accounts Context can emit the AccountClosed
event when an investor discontinues their relationship with the brokerage, or if a corrective measure is taken by the brokerage itself. The fully-qualified schema registry path is:
That is, the Org is named VLINGO
, the Unit is named Brokerage
, and the Context is named from the project's base package name io.vlingo.brokerage.accounts
. Following the basic path to the Context, the Schema Name is provided as AccountsClosed
and the Schema Version is 1.0.0
. This event schema and version is registered with XOOM Schemata, selected through the UI integration, and will be automatically pulled from the registry as a Java class source file. The included schema source is an unmodifiable dependency. Thus, type safety is retained across the system, even in messages exchanged between subsystems.
Exchange Configuration
By default the producer/consumer exchanges are backed by the RabbitMQ implementation of XOOM Lattice Exchange. The generated project will contain the required properties and classes to connect the service/application to the message broker. Also, when the service/application is initialized, the exchanges and its listening queues will be automatically created if any do not exist.
Although RabbitMQ is the default messaging system that we provide with code generation, you are certainly not limited to its use. The XOOM Lattice Exchange implementations include one for Apache Camel, which supports 320 or more messaging mechanisms. Among the supported brokers and buses are Kafka, AWS SNS and SQS, Google Cloud Pub/Sub, Azure ServiceBus, and many others.
The exchange settings can be found at /src/main/resources/xoom-turbo.properties
in your generated project directory. The following shows how this works for our running example:
Based on these settings, XOOM Lattice Exchange will create a fanout exchange. Thus, before starting your application, ensure that RabbitMQ is running on the configured host/port and that the credentials are valid. In addition, the listening queues of a consumer exchange need to be wired to the producer exchange. There are different ways to set up the exchange queues. One of them is to manually bind the queues using the RabbitMQ Management. The management console has a self-explanatory user interface making the exchange configuration straightforward. Below is a demonstration of how to bind the queues of a service/application to the proper exchanges.
The first step is to access the Exchanges section on RabbitMQ management.
Keep in mind that this example demonstrates that the Investor
context consumes events from Account
context. So, the next step is to click on the Account
topic and bind the Investor
listening queue. As the following image shows, the Account
topic initially publishes messages only to its own listening queue.
The first field of the Add binding from this exchange form needs to be filled in with the queue name of the consumer exchange. In this case, the self-listening queue related to the Investor
exchange as seen in the following screenshot.
After filling in the queue name and clicking on the Bind button, the Investor queue is present in the binded queues list and all messages published through the Account
exchange will be delivered to the Investor context.
The Account
exchange can now be consumed by the Investor
service.
Step 4: Persistence
The fourth step is used to define the persistence Storage Type, whether or not CQRS is to be used, how Query/Read Model projections are to work, and what database storage engines are to be used.
Note that when using Event Sourcing, all message handlers must emit an event.
The selection of Storage Type is important to determining the underlaying Aggregate base class. Consider the two possible options:
Journal for Event Sourcing
State Store for Key-Value Persistence
When Journal for Event Sourcing is selected, all Aggregates in this model will be subclasses of EventSourced
, meaning that they will use Event Sourcing persistence.
When State Store for Key-Value Persistence is selected, all Aggregates in this model will be subclasses of StatefulEntity
, meaning that persistence will be to key-value (i.e. NoSQL) database.
If using CQRS, there will be both a Command Model and a Query Model. That means two basic things:
Query Model views must be projected from either
StatefulEntity
full state, or from events emitted out ofEventSourced
entities. ActuallyStatefulEntity
types may also project Query Model views from events. Yet, this requires every command message handler to emit and event, which might go against the desired model design.A database storage engine must be selected for both models, one for the Command Model and one for the Query Model.
There are several database storage engines available and more will be available in the future.
Step 5: Deployment
The fifth step defines the deployment container types.
The project generator provides native packaging, such as Java JAR (choice Default). You may instead use containerization files facilitating Docker and Kubernetes deployment.
Local Docker Image: The name of the Docker image.
Published Docker Image: For Kubernetes only; name of published image.
Kubernetes POD: For Kubernetes only; name of POD.
HTTP Server Port: The port on which the REST requests are made to this service.
Producer Exchange Port: For future use.
Cluster Port: The start of port range for each cluster node, two per node; total ports used will be Cluster Total Nodes * 2, with port numbers increasing from this port.
Cluster Total Nodes: The maximum number of cluster nodes.
Step 6: Generation
The sixth and final step defines project component types, and generates the project.
You may choose to generate a ReactJS scaffolding UI for basic REST resource editing.
We highly recommend using both annotations and auto-dispatch features. The XOOM platform does not provide many annotations, but the ones that it does provide are very useful. The auto-dispatch feature is how incoming REST requests and consumer exchange/topic messages to automatically translated and dispatched to the appropriate Aggregate instance in the domain model.
The user interface show in the previous figure supports generating and downloading the project as a ZIP file. This runtime mode of the XOOM Designer is available by including a specific command-line option when executing the application, namely zip-download
. This is the only possible choice when running XOOM Designer from a Docker image:
If you don't run with that option the XOOM Designer will generate projects directly to a local hard drive directory. The following figure shows the user interface for the local file-based generation:
In this case, a local filesystem directory will be generated for you, but you may override it by entering a different project parent folder. In addition, select whether VLINGO XOOM annotations and auto-dispatch are preferred, or not. Click Generate to start cause the project generation to the specified directory.
Once the six steps have been completed and the service project is generated, take full advantage of the power of the VLINGO XOOM acceleration components. Use the platform comprehensive documentation and its collaborative community that supports you and other developers on your journey. Now, go have fun!
Collaboration
Our team really appreciates collaboration, not only because it boosts VLINGO XOOM to greater value, but also for the fact that the more viewpoints we have the more competent and mature the VLINGO XOOM community becomes. If you want to be a catalyst for moving the platform forward, take a tour of our development guide.
Last updated