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:

  1. Select a Programming Language and Platform (currently Java only)

  2. Define your top-level Bounded Context name and module

  3. Visually design Aggregates, even with Value Objects, collections, and Domain Events

  4. Choose persistence type options, including Event Sourcing, Key-Value, and CQRS

  5. Specify how your Bounded Context will be deployed

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

 $ curl -L -O https://raw.githubusercontent.com/vlingo/xoom-designer/master/docker-compose.yml
 $ docker-compose pull
 $ docker-compose up -d

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:

docker run -it --rm -eXOOM_ENV=dev -p '9019:9019' vlingo/xoom-schemata

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:

docker run -it --rm -eXOOM_ENV=dev -p9019:9019 --name xoom-schemata vlingo/xoom-schemata

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

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

File type: tar

$ curl -L -O https://github.com/vlingo/xoom-designer/releases/download/1.9.0/designer.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:

 $ chmod 755 xoom

Ensure it's all set by verifying the version:

 $ ./xoom -version
 1.9.0

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

$ ./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:

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:

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:

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

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

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

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.

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:

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

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.

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

  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.

For the Investor type, one such message with a 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?

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

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:

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

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() {
    // from: return new InvestorState(this.id, this.name, this.suspended);
    // to:
    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 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.

  1. The state objects function that deals with the specific transition must be refactored. This was just explained above.

  2. 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 is false, but the Designer will inevitably generate tests with true, because we consider that the positive state perspective is more common. All test cases that consider the default state as true must be adjusted to false instead.

  • It might seem that all cases where the state is transitioned from false to true require adjustment, but actually the default of true 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:

  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:

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 a collection of resources, such as /investors.

  2. 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 will PUT, PATCH, or DELETE the resource. This is seen in the second example.

  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 figures for a visual mapping of the concepts expressed next.

These exchange parts must be provided:

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

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

  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:

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:

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, 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:

exchange.names=investor-topic

exchange.investor-topic.hostname=localhost
exchange.investor-topic.username=guest
exchange.investor-topic.password=guest
exchange.investor-topic.port=5672
exchange.investor-topic.virtual.host=/

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

$ 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:

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