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

If you are trying to learn Domain-Driven Design and the Microservices Architecture for the first time, you will face challenges. The same goes for Reactive Architecture and Reactive Programming.

Due to the steep learning curve that some face, many will give up on using these concepts and paradigms. That's disappointing given that these are proven to be a strong foundation for building modern applications and systems that are robust, modularized, scalable, and that use modern architectures. When the learning curve is too steep, many developers tend to fall back to familiar, yet outdated, frameworks and tools.

Yet, none of the topics--Domain-Driven Design, Microservices Architecture, as well as Reactive Architecture and Reactive Programming--are of necessity too difficult to grasp and put to use quickly. To accelerate learning and application 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.

The XOOM Designer provides modeling tools to rapidly deliver software solutions.

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.

With an 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 with full life-cycle. The 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 so that you can use the tools. The following explains both cloud and local access.

Running Locally Using Our Docker Image

The quickest way to run the XOOM Designer locally is to use the Docker image published by the VLINGO XOOM Team.

Remember to expose the 19090 port and mount the volume for your projects to /designer/VLINGO-XOOM. Here's how that works on *nix:

$ docker run -it --rm -p '19090:19090' -v $(pwd)/projects:/designer/VLINGO-XOOM vlingo/xoom-designer

This is how you can do the same on Windows:

C:\> docker run -it --rm -p '19090:19090' -v C:/projects:/designer/VLINGO-XOOM vlingo/xoom-designer

The Docker image cannot automatically open your preferred browser on the XOOM Designer URL. You will have to navigate manually using: http://localhost:19090/context

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.x.x

  • Docker Desktop 18.x

Download the XOOM Designer compressed distribution file using curl.

File type: zip

$ curl -L -O https://github.com/vlingo/vlingo-xoom-starter/releases/latest/download/starter.zip

File type: tar

$ curl -L -O https://github.com/vlingo/vlingo-xoom-starter/releases/latest/download/starter.tar

Extract the file content, then set an environment variable named VLINGO_XOOM_STARTER_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:

$ chmod 755 xoom

Ensure it's all set by verifying the version (version shown is an example, not necessarily current):

$ ./xoom -version
1.4.4

The XOOM Designer provides a web/graphical user interface for a rapid application generation. Simply open a terminal window and run the Starter.

$ ./xoom gui

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:

An EventStorming session segment of a design-level model.

The EventStorming model indicates that the following model collaborations and outcomes happen:

  1. The Register command message is sent to the Investor Aggregate, which is a new instance and is initialized by handling the Register message

  2. The Investor runs any business rules, validates the command, and applies the new InvestorRegistered event

  3. An 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)

  4. The incoming event is translated to a Suspend command that is sent as a command message to the Investor

  5. The Investor accepts the Suspend command message, validates it, and applies the new InvestorSuspended 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:

Stack the elements of the design-level EventStorming results by Aggregate type.

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:

As EventStorming elements are stacked, the XOOM Designer displays Aggregates similarly.

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:

  1. A REST request arrives a resource request handler registered with XOOM HTTP

  2. 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

  3. 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

  4. 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

  5. When the persistence confirmation is received by the underlying Aggregate base class implementation, the concrete Aggregate is informed to mutate its state accordingly

  6. 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.

  1. Currently you may model for the Java platform or the .NET platform (available soon).

  2. A domain model is the primary focus of the modeling, and the model is designed using DDD tactical modeling tools.

  3. All architectural decisions are made to support the domain model, and the architectural mechanisms include the application of the DDD Context Mapping patterns.

  4. 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 currently no Platform selection step because only the Java platform is available at this time. Soon there will be a Platform selection step. It can be seen here.

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 standard groupId. An example of such is io.vlingo.brokerage, which will be inserted in the project's pom.xml as the following: <groupdId>io.vlingo.brokerage</groupId>

    • Artifact Id: Enter the Maven pom.xml standard artifactId. An example of such is investor, which will be inserted in the project's pom.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 artifact version, which is a semantic version. An example of such is 1.0.0, which will be inserted in the project's pom.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 and io.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.

Step 2: JVM Platform: Enter the Context information.

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.

  • An Aggregate is a domain model Entity, which has a unique identity and a state. An Aggregate designed in the Investor Context is Investor. Another is BuyOrder.

  • The tool presents an opinion 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 named InvestorState.

  • 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.

Step 3: Declare a new Aggregate type.

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.

Step 3: Fill in the Aggregate type name, state, events, commands, API, and exchange data.

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 List<Money>.

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, or side-effect-free behavior. This means that every state 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.

package io.vlingo.brokerage.investor.model.investor;
import io.vlingo.brokerage.investor.model.*;
public final class InvestorState {
public final String id;
public final FullName name;
public static InvestorState identifiedBy(final String id) {
return new InvestorState(id, null);
}
public InvestorState(final String id, final FullName name) {
this.id = id;
this.name = name;
}
public InvestorState changeName(final FullName name) {
return new InvestorState(this.id, name);
}
public InvestorState register(final FullName name) {
return new InvestorState(this.id, name);
}
...
}

The Aggregate accepts and handles the changeName(FullName) command message, which causes the state to transition.

public class InvestorEntity extends EventSourced implements Investor {
private InvestorState state;
...
@Override
public Completes<InvestorState> changeName(final FullName name) {
final InvestorState stateArg = state.changeName(name);
return apply(new InvestorNameChanged(stateArg), () -> state);
}
private void applyInvestorNameChanged(final InvestorSuspended event) {
state = state.changeName(event.name);
}
...
}

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.

package io.vlingo.brokerage.investor.model.investor;
import io.vlingo.brokerage.investor.model.*;
public final class InvestorState {
public final String id;
public final FullName name;
public final boolean suspended;
public final List<Money> balanceSheet = new ArrayList<>();
public static InvestorState identifiedBy(final String id) {
return new InvestorState(id, null, false);
}
public InvestorState(final String id, final FullName name, final boolean suspended) {
this.id = id;
this.name = name;
this.suspended = suspended;
}
public InvestorState changeName(final FullName name) {
return new InvestorState(this.id, name, this.suspended);
}
public InvestorState register(final FullName name) {
return new InvestorState(this.id, name, this.suspended);
}
public InvestorState suspend() {
return new InvestorState(this.id, this.name, true);
}
...
}

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:

Step 3: Define one or more Value Objects to be used by at least one Aggregate type.

The following table shows the three Value Object types defined for an used in the Investor Contexts.

Value Object Type

Field Name

Field Type

BuyDetails

symbol

String

price

Money

shares

int

FullName

first

String

last

String

Money

value

String

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.

package io.vlingo.brokerage.investor.model.investor;
import io.vlingo.xoom.common.version.SemanticVersion;
import io.vlingo.xoom.lattice.model.IdentifiedDomainEvent;
import io.vlingo.brokerage.investor.model.*;
public final class InvestorRegistered extends IdentifiedDomainEvent {
public final String id;
public final FullName name;
public InvestorRegistered(final InvestorState state) {
super(SemanticVersion.from("1.0.0").toValue());
this.id = state.id;
this.name = state.name;
}
@Override
public String identity() {
return id;
}
}

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.

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

  1. 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

  2. Parameters, which are selected among those declared as State Fields, or none

  3. Event, which is selected from among those declared in the Events section

The following figure shows this, and following it more detail is provided.

Step 3: The Aggregate information includes Event and Command Method declarations.

For the Investor type, one such message and corresponding message handler is the method register(FullName name).

  1. Enter the message handler method name register

  2. Select the parameters to the message that the register() method accepts drop-down. In this case the only parameter is name. For this message handler, as explained next, there is no need to select the id as a parameter.

  3. The register() method is a creational or factory method because it is used to initialize the Investor state. Thus, toggle on the option: Involves creation of entity?

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:

public class InvestorEntity extends EventSourced implements Investor {
private InvestorState state;
public InvestorEntity(final String id) {
super(id);
this.state = InvestorState.identifiedBy(id);
}
...
@Override
public Completes<InvestorState> register(final FullName name) {
final InvestorState stateArg = state.register(name);
return apply(new InvestorRegistered(stateArg), () -> state);
}
private void applyInvestorRegistered(final InvestorRegistered event) {
state = state.register(event.name);
}
...
}

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 ever passed as a parameter, if ever.

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.

public class InvestorEntity extends EventSourced implements Investor {
private InvestorState state;
...
@Override
public Completes<InvestorState> suspend() {
final InvestorState stateArg = state.suspend();
return apply(new InvestorSuspended(stateArg), () -> state);
}
private void applyInvestorSuspended(final InvestorSuspended event) {
state = state.suspend();
}
...
}

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:

package io.vlingo.brokerage.investor.model.investor;
import io.vlingo.brokerage.investor.model.*;
public final class InvestorState {
public final String id;
public final FullName name;
public final boolean suspended;
...
public InvestorState suspend() {
return new InvestorState(this.id, this.name, true);
}
...
}

Take another look at the InvestorEntity source used to transition state:

public class InvestorEntity extends EventSourced implements Investor {
private InvestorState state;
...
@Override
public Completes<InvestorState> suspend() {
final InvestorState stateArg = state.suspend();
return apply(new InvestorSuspended(stateArg), () -> state);
}
private void applyInvestorSuspended(final InvestorSuspended event) {
state = state.suspend();
}
...
}

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 knowledge of the Aggregate.

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.

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:

  1. 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

  2. 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

  3. 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:

Step 3: The architecture elements surround the Aggregate are driven by it.

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:

  1. Root Path: The root or anchor URI path that all detailed REST requests will have, which is often only a /

  2. Path: The URI path that extends beyond the Root Path, which generally points to a resource collection within which to POST, or a fully identified entity URI to which a request will PUT, PATCH, or DELETE the resource

  3. HTTP Request Method: POST, GET, PUT, PATCH, DELETE, HEAD, or OPTIONS

  4. 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 figure for a visual mapping of the concepts expressed next. These exchange parts must be provided:

  1. Exchange Name: 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. Provide a meaningful name.

  2. Schema Registry Path: The above UI figure shows 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 schema is a part. Note that the Context will likely be the same used in the Context page for the Base Package Name. These are each separated by colons.

  3. 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:

Step 3: The Producer Exchange publishes Aggregate events as messages.

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.

Step 3: A Consumer Exchange subscribes to events (etc) that are dispatched to the Aggregate.

The Consumer Exchange show 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:

vlingo:brokerage:io.vlingo.brokerage.accounts:AccountClosed:1.0.0

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 and will be automatically pulled from the registry as a Java class, included in the consuming context as a non-modifiable dependency. Thus, type safety is retained across the system, even in messages exchanged between subsystems.

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.

Step 4: Persistence is determined by Storage Type, CQRS, Projections, and database(s).

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 of EventSourced entities. Actually StatefulEntity 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 much 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 None). You may instead use containerization files facilitating Docker and Kubernetes deployment.

Step 6: Generation

The sixth and final step defines project component types, and generates the project.

Step 6: Generate the project and automatically download it as a ZIP file.

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:

$ java -jar target/xoom-designer-1.7.7.jar gui --target zip-download

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:

Step 6: Generate the project directly to a directory on local filesystem hard drive.

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!

Using the Command-Line Interface

Alternatively, you can also generate applications directly from the terminal through xoom gen command. In this case, the project settings have to be informed in a properties file under the vlingo-xoom-designer folder.

This was our original bootstrap to get the XOOM Designer up and running without a graphical web user interface. Although it served a purpose, it has fallen behind in power compared to the web user interface. You might still find it useful, but please don't judge the full XOOM Designer capabilities by the CLI.

See a commented properties file sample below:

#Maven artifact version
version=1.0.0
#Maven project group id
group.id=com.company
#Maven artifact version
artifact.id=xoom-application
#Base package name
package=com.company.business
#Absolute path for the project parent folder
target.folder=/home/projects
#Deployment Type (NONE, DOCKER, KUBERNETES)
deployment=DOCKER
#Docker Image name, required if deployment type is KUBERNETES or DOCKER
docker.image=xoom-app
#Published Docker Image, required if deployment type is KUBERNETES
k8s.image=xoom-application
#Kubernetes POD name, required if deployment type is KUBERNETES
k8s.pod.name=xoom-application
#Storage Type (STATE_STORE or JOURNAL)
storage.type=STATE_STORE
#CQRS (true or false)
cqrs=true
#Projections Type (NONE, EVENT or OPERATION)
projections=EVENT
#Domain Model Database, required if CQRS is false (IN_MEMORY, POSTGRES, HSQLDB, MYSQL, YUGA_BYTE)
database=HSQLDB
#Command Model Database, required if CQRS is true or Storage Type is Journal (see database types above)
command.model.database=MYSQL
#Query Model Database, required if CQRS is true or Storage Type is Journal (see database types above)
query.model.database=YUGA_BYTE
#Use Xoom Annotations, when applicable
annotations=true
#Use Auto Dispatch Resources
auto.dispatch=true
#Xoom version
vlingo.xoom.version=1.4.1-SNAPSHOT

Executing xoom gen causes the application generation based on the settings above.

Containerization Commands

vlingo-xoom-designer provides cool shortcuts for interacting with Docker, Kubernetes, and Gloo. The following commands have to be run from the project root folder:

Command

Description

Options

xoom docker status

Shows the container status. Should be executed on the project root folder.

N/A

xoom docker package

Build / update the Docker image from the application's current state. Should be executed on the project root folder.

tag: relate a tag to the current image build. If not informed, the default value is latest. Example: xoom docker package --tag 1.0

xoom docker push

Publishes the image into the configured docker repository. Should be executed on the project root folder.

tag: relate the local tag to the remote tag. If not informed, the default value is latest:latest, following the pattern local-tag:remote-tag. Example: xoom docker push --tag 1.0:latest

xoom k8s push

Apply the manifest file(s) placed under deployment/k8s on Kubernetes

N/A

xoom gloo init

Install the Gloo Gateway API generating an upstream for each running service on Kubernetes.

N/A

xoom gloo suspend

Uninstall the Gloo Gateway API.

N/A

xoom gloo route

Create routes, on Gloo Gateway API, for endpoints declared in vlingo-xoom.properties

In vlingo-xoom.properties , define the options described below:

gloo.upstream: a Gloo upstream name for the app's service running on K8s. Example: gloo.upstream = default-myxoomapp-8080

gloo.resource.[resource-name]: an endpoint for an application resource identified by resource-name. Example: gloo.resource.balance = v1/balance gloo.gateway.[resource-name]: a gateway route corresponding to a mapped endpoint identified by resource-name. Example: gloo.gateway.balance= balance Note that, for each resource, a pair of gloo.resource / gloo.gateway has to be informed for properly creating a route in the Gloo Gateway API.

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.