Most organizations are adopting an event-driven architecture (EDA) to compete in a world where customer satisfaction requires real-time outcomes.

In this code lab we'll build and expand your toolbox by learning how an Event Portal, paired with industry standard specifications and frameworks, enable a smooth journey to bring your EDA from initial architecture and design to code running in production while also setting your team up for success as the business needs, architecture and applications themselves are enhanced over time.

Throughout this workshop we will get hands on and talk about:

PubSub+ Event Portal

AsyncAPI

🛠 This page covers the setup needed to perform this codelab. 🛠

AsyncAPI Generator Requirements

✅ Install instructions available here

We'll install the generator itself later 👍

Java / Spring Cloud Stream Requirements

✅ Spring Cloud Stream just requires Java and Maven to use 🚀

Node.js Requirements

✅ There are only a few requirements for the Node.js steps!

PubSub+ Event Broker Connection Info

✅ The credentials below are for a public event feed found on the Solace feed Marketplace that we'll use during this codelab.

✅ Note that this client-username has permissions to subscribe to taxinyc/> and test/taxinyc/> and permissions to publish to test/taxinyc/>

Sign-up for Solace Cloud

✅ If you already have a Solace Cloud account just login, otherwise please sign-up for a free Solace Cloud Account using this link. Note that no credit card is required. You will receive an email to activate the account and will then be prompted to start the free trial.

sc_trial

Import Existing Designed EDA

✅ Download the Application Domain export file: EventPortalExport_Initial.json

You can download the file via curl or by cloning the git repo

curl -k -XGET https://raw.githubusercontent.com/Mrc0113/ep-design-workshop/main/EventPortalExport_Initial.json -o EventPortalExport_Initial.json

OR

git clone https://github.com/Mrc0113/ep-design-workshop.git

✅ Inside your logged into Solace Cloud Account navigate to the Event Portal Designer by clicking "Designer" in the menu on the left.

ep_select_designer

✅ Then import the previously downloaded Application Domain file by clicking the Import button at the top right of the Designer and importing the file.

ep_click_import

🚀 Setup complete! Let's get going! 🚀

You are a member of the engineering team at the NYC Modern Taxi Co, a fictional taxi cab company based in New York City. Your team is playing from behind and racing to catch up with technology innovation introduced to the industry by Rideshare competitors such as Uber and Lyft. In order for the company to survive and eventually thrive your team has convinced the board that transforming the companies' IT systems is of utmost importance. Your team has done its research and determined that moving to an Event-Driven Architecture is essential to future rapid innovation and has already kicked this initiative off by deploying a Solace Event Mesh and updating the taxi fleet to stream real-time events that include ride and location information. We know what the fleet is up to! Now it's time to start to continually improve and provide a world class customer experience.

In order to react in a real-time manner the team has decided that we want to process the updates as they stream in from the fleet of taxis instead of putting them directly into a datastore and then having to retrieve them to do processing later. To prototype this work, you'll see a high level design in the diagram below. Since we already have the taxi fleet streaming their updates into our PubSub+ Event Mesh we need to do the following:

  1. 🚖 Create and capture this design in the PubSub+ Event Portal where we can define our Event-Driven Architecture, including its components: Applications, Events, and Schemas. This will allow us to define the details needed to implement, visualize and extend the architecture as it evolves, and share/collaborate with our entire engineering team as we continue to innovate.
  2. 🚕 Next up we're going to document few applications and events so that they can be understood and reused by others.
  3. 🚕 Learn, Understand and Reuse some of our events in a new use case
  4. 🚖 Lastly we'll develop the ProcessPayment microservice that that receives the stream of RideUpdated events, charges the customer's credit card and generate a PaymentCharged Event.

Architecture

Terms of Use: This dataset is publicly available for anyone to use under the following terms provided by the Dataset Source — https://data.cityofnewyork.us/ — and is provided "AS IS" without any warranty, express or implied, from Solace. Solace disclaims all liability for any damages, direct or indirect, resulting from the use of the dataset.

Before we dive deeper, let's ensure we are all aligned with terminology of the objects and concepts we will use in PubSub+ Event Portal.

Application Domain & Workspace

An application domain represents a namespace where applications, events, and schemas can live. Within this namespace, you can create a suite of applications, events, and schemas that are independent of other application domains. In our NYC Taxi use case we introduced earlier, we may group applications into different domains, for ex. we may have a domain for our rideshare apps and services, one for our back-office apps where invoicing and background checks are being processed, and maybe another domains for analytics where we group apps that are responsible for analyzing the successful operation of our rideshare services.

Workspace Example

Events/Topics

Events are an important part of the Event Portal. Think of an event as a concept of the publish-subscribe (pub/sub) architectural pattern. Topics are used to route data or events (in the form of messages) between distributed applications, often using a message broker or an event broker.

A Solace topic and an Apache Kafka topic might seem fundamentally the same, but there are quite a few differences between them.

Here are some examples from our use case:

Kafka Topics:

Solace Topics:

Schemas

In simple terms, a schema represents the contract to describe the payload of an event. Producers and consumers of an event can trust that the event's payload matches the schema definition assigned to that event. Schemas define a type of payload through JSON, AVRO, XML, Binary, or Text. JSON, AVRO, and XML schemas have content that describes each property of the schema.

In our use case all events are in JSON Schema format.

Schema Example

Applications

An application represents a piece of software that produces and consumes events. Applications connect to the event broker in an event-driven architecture and communicate with other applications via events. A single application represents a class of applications that are running the same code base; therefore, a Kafka consumer group can be associated with an Application object in the Event Portal.

Decomposing the Enterprise

It is important to consider how your enterprise is organized so that it can be decomposed using the Application Domain construct. An Application Domain provides the ability to organize and decompose an enterprise into logical groupings. These groupings could be based on-line of business, related functional capabilities or based on team dynamics. The benefits of doing this includes:

  1. Event sharing rules – decide which events should be shared with other application domains and those which are for internal application domain usage only. This has implications both from a security perspective, but also which events need to be managed more tightly as they affect others outside the application domain
  2. Provide uniform event topic prefixes – ensures that the prefix is unique and that topic best practices are followed

Topic Naming Best Practices

aside The topic of which an event is addressed seems like a pretty simple decision, but in reality, it can result in some negative consequences if not planned >n advance. A topic is more than an address, it is metadata that describes the event and can be used for several purposes such as routing, access control and versioning. Thus, it is important to properly govern and manage the topic structure. Regardless of your broker type, it is a good practice to make topics structured and hierarchical the same way a RESTful Resource uses hierarchical addressing. In other words we want to produce hierarchical topics that rank from least specific to most specific.

Parts of the Event Topic

The event topic structure has two parts:

  1. The Event Topic Root contains enough information to describe the type of event that has occurred. Each Event Topic Root is a static field that describes the type of event. The list of Event Topic Roots forms a catalog of events that can be produced and consumed. This catalog could be brought into the PubSub+ Event Portal's event catalog, listing each event type along with details about the event. Each Event Topic Root describes the event in as much detail as necessary to map it to a single data schema.
  2. The Event Topic Properties are optional fields that further describe a particular event. This part of the topic has fields that are dynamically filled when the producer publishes the event. These fields are used to describe the specific or unique attributes of this event instance that would be used for routing and filtering.

Event Information Exchange Patterns

There are multiple Event Exchange Patterns (EEP) that should be considered when using EDA:

Thin Event Notification

Hypermedia-Driven Events

Event-Carried State Transfer

For more information about discovering, organizing and enhancing your Event-Driven Architecture, review [Understand, Manage, and Enhance Your Event-Driven Architecture Lifecycle ] (https://docs.solace.com/Cloud/Event-Portal/get-started-event-portal-user-scenario.htm)

By designing a new event-driven application or extending your event-driven architecture, you're able to deliver new real-time business capabilities in a decoupled and reusable fashion. There are however several key elements which should be considered when designing events, schemas and applications including topic best practices, options for exchanging event data and sharing/visibility rules. Considering these things early will put you on the road to success and enable better reusability down the road.

Now that you're familiar with the use case 🚕 🚖 🚕 and you've imported the application domain into the Event Portal, let's update our Event-Driven Architecture (EDA).

Lets say that your tasked with working within the Back Office team (where the cool kids all work) and are asked to architect the way in which we will charge our passengers for their rides and if the passenger is part of a commercial account, send to our Invoicing System. This is composed of 4 steps

  1. Ideate
  2. Design the schema
  3. Design the event
  4. Design the applications

Step 1: Determine What Can Trigger Payment - Ideate

So essentially we need to consider, is there a business event that would help us trigger on the moment when the ride has been completed?

  1. Navigate to the Catalog component of the Event Portal
  2. Click on the Schemas tab and search for "dropoff"
  3. In the Search Results click on the RideUpdated event in order to understand the matching text context.
  4. We now know that the RideUpdated Schema has a field called ride_status that can have a value of dropoff. So how do we get access to that data? Click on the RideUpdated schema and we will find out!
  5. We now see the metadata about the RideUpdated schema and at the bottom we can see there is an Event that references this schema called RideUpdated. The topic being used leverages the ride_status attribute which is pretty sweet! So we can filter on dropoff as a client.

Step 2: Design the PaymentCharged Schema

Next we should decide what we want the data to look like once we have processed a payment.

  1. First we must decide what Event Exchange Pattern (EEP) we will use. For Maximum flexibility, and because time is not of the essence, we will leverage "Event-Carried State Transfer".
  2. Click into the Designer component of the Event Portal
  3. Double-click on the NYC Modern Taxi Co - Back Office Application Domain and its time to get creating!
  4. Click on the Schema tab
  5. On the Upper Right Corner, Click the Create button and select Create Schema
  6. Create Schema by setting values:
    1. Name: PaymentCharged
    2. Content Type: JSON
    3. Shared: YES
    4. Owner: Assign Yourself
    5. Tags: NONE
    6. Description: NONE
    7. Versions: Leave unchecked
    8. Content:
{
  "$schema": "http://json-schema.org/draft-07/schema",
  "$id": "http://example.com/example.json",
  "type": "object",
  "title": "The root schema",
  "description": "The root schema comprises the entire JSON document.",
  "default": {},
  "examples": [
    {
      "payment_charged_id": "23232323",
      "timestamp": "2020-06-03T16:51:47.29612-04:00",
      "information_source": "ProcessPayment",
      "payment_status": "accepted",
      "invoice_system_id": "PSG-32923",
      "amount_charged": 12.32,
      "ride_id": "545496c5-a334-4344-9662-efde68c0b98a",
      "entity_type": "Driver",
      "driver": {
        "driver_id": 1234132,
        "first_name": "Frank",
        "last_name": "Smith",
        "rating": 4,
        "car_class": "SUV"
      },
      "passenger": {
        "passenger_id": 2345243,
        "first_name": "Jesse",
        "last_name": "Menning",
        "rating": 2
      }
    }
  ],
  "required": [
    "payment_charged_id",
    "timestamp",
    "information_source",
    "payment_status",
    "invoice_system_id",
    "amount_charged",
    "ride_id",
    "entity_type",
    "driver",
    "passenger"
  ],    
  "properties": {
    "payment_charged_id": {
      "$id": "#/properties/payment_charged_id",
      "type": "string",
      "title": "The payment_charged_id schema",
      "description": "An explanation about the purpose of this instance.",
      "default": "",
      "examples": ["23232323"]
    },
    "timestamp": {
      "$id": "#/properties/timestamp",
      "type": "string",
      "title": "The timestamp schema",
      "description": "An explanation about the purpose of this instance.",
      "default": "",
      "examples": ["2020-06-03T16:51:47.29612-04:00"]
    },
    "information_source": {
      "$id": "#/properties/information_source",
      "type": "string",
      "title": "The information_source schema",
      "description": "An explanation about the purpose of this instance.",
      "default": "",
      "examples": ["ProcessPayment"]
    },
    "payment_status": {
      "$id": "#/properties/payment_status",
      "type": "string",
      "title": "The payment_status schema",
      "description": "An explanation about the purpose of this instance.",
      "default": "",
      "examples": ["accepted"]
    },
    "invoice_system_id": {
      "$id": "#/properties/invoice_system_id",
      "type": "string",
      "title": "The invoice_system_id schema",
      "description": "An explanation about the purpose of this instance.",
      "default": "",
      "examples": ["PSG-32923"]
    },
    "amount_charged": {
      "$id": "#/properties/amount_charged",
      "type": "number",
      "title": "The amount_charged schema",
      "description": "An explanation about the purpose of this instance.",
      "default": 0,
      "examples": [12.32]
    },
    "ride_id": {
      "$id": "#/properties/ride_id",
      "type": "string",
      "title": "The ride_id schema",
      "description": "An explanation about the purpose of this instance.",
      "default": "",
      "examples": ["545496c5-a334-4344-9662-efde68c0b98a"]
    },
    "entity_type": {
      "$id": "#/properties/entity_type",
      "type": "string",
      "title": "The entity_type schema",
      "description": "An explanation about the purpose of this instance.",
      "default": "",
      "examples": ["Driver"]
    },
    "driver": {
      "$id": "#/properties/driver",
      "type": "object",
      "title": "The driver schema",
      "description": "An explanation about the purpose of this instance.",
      "default": {},
      "examples": [
        {
          "driver_id": 1234132,
          "first_name": "Frank",
          "last_name": "Smith",
          "rating": 4,
          "car_class": "SUV"
        }
      ],
      "required": [
        "driver_id",
        "first_name",
        "last_name",
        "rating",
        "car_class"
      ],
      "properties": {
        "driver_id": {
          "$id": "#/properties/driver/properties/driver_id",
          "type": "integer",
          "title": "The driver_id schema",
          "description": "An explanation about the purpose of this instance.",
          "default": 0,
          "examples": [1234132]
        },
        "first_name": {
          "$id": "#/properties/driver/properties/first_name",
          "type": "string",
          "title": "The first_name schema",
          "description": "An explanation about the purpose of this instance.",
          "default": "",
          "examples": ["Frank"]
        },
        "last_name": {
          "$id": "#/properties/driver/properties/last_name",
          "type": "string",
          "title": "The last_name schema",
          "description": "An explanation about the purpose of this instance.",
          "default": "",
          "examples": ["Smith"]
        },
        "rating": {
          "$id": "#/properties/driver/properties/rating",
          "type": "number",
          "title": "The rating schema",
          "description": "An explanation about the purpose of this instance.",
          "default": 0,
          "examples": [4]
        },
        "car_class": {
          "$id": "#/properties/driver/properties/car_class",
          "type": "string",
          "title": "The car_class schema",
          "description": "An explanation about the purpose of this instance.",
          "default": "",
          "examples": ["SUV"]
        }
      },
      "additionalProperties": true
    },
    "passenger": {
      "$id": "#/properties/passenger",
      "type": "object",
      "title": "The passenger schema",
      "description": "An explanation about the purpose of this instance.",
      "default": {},
      "examples": [
        {
          "passenger_id": 2345243,
          "first_name": "Jesse",
          "last_name": "Menning",
          "rating": 2
        }
      ],
      "required": ["passenger_id", "first_name", "last_name", "rating"],
      "properties": {
        "passenger_id": {
          "$id": "#/properties/passenger/properties/passenger_id",
          "type": "integer",
          "title": "The passenger_id schema",
          "description": "An explanation about the purpose of this instance.",
          "default": 0,
          "examples": [2345243]
        },
        "first_name": {
          "$id": "#/properties/passenger/properties/first_name",
          "type": "string",
          "title": "The first_name schema",
          "description": "An explanation about the purpose of this instance.",
          "default": "",
          "examples": ["Jesse"]
        },
        "last_name": {
          "$id": "#/properties/passenger/properties/last_name",
          "type": "string",
          "title": "The last_name schema",
          "description": "An explanation about the purpose of this instance.",
          "default": "",
          "examples": ["Menning"]
        },
        "rating": {
          "$id": "#/properties/passenger/properties/rating",
          "type": "number",
          "title": "The rating schema",
          "description": "An explanation about the purpose of this instance.",
          "default": 0,
          "examples": [2]
        }
      },
      "additionalProperties": true
    }
  },
  "additionalProperties": true
}


  1. Revision Comment: "Initial Creation of Schema"
  2. Click Save

Step 3: Design PaymentCharged Event

So now that we have constructed the payload format for the PaymentCharged event, it is time to design the event itself. What's involved? Well we need to apply our best practices as it comes to the Topic name!

  1. Click into the Designer component of the Event Portal
  2. Double-Click on the NYC Modern Taxi Co - Back Office Application Domain
  3. Click on the Events tab
  4. On the Upper Right Corner, Click the Create button and select Create Event
  5. Create Event by setting values:
    1. Name: PaymentCharged
    2. Shared: YES
    3. Description: NONE
    4. Logical Event Mesh: NYC Modern Taxi Logical Event Mesh (default selection)
    5. Description: NONE
    6. Topic Address: Click on "Set Topic Address"
      • As you can see the domain already has some "Event Topic Root" taxinyc/backoffice/
      • We need to apply the best practice of Domain/ObjectType/Verb/Version/Locality/SourceID/ObjectID to this event
      • We will use the topic name of: taxinyc/backoffice/payment/charged/v1/{payment_status}/{driver_id}/{passenger_id}
      • We will create topic levels {payment_status}, {driver_id} and {passenger_id} as variables
    7. Value: Keep the Schema radio button selected
    8. Choose the Schema "PaymentCharged" that we created in the previous step
    9. Owner: Assign Yourself
    10. Tags: NONE
    11. Revision Comment: "Initial Creation of Event"
    12. Click Save

Step 4a: Design ProcessPayment Application

Now for the fun part! We need to design the event-driven interface of the ProcessPayment Application. This is pretty easy as it has one input which triggers a single output.

  1. Click into the Designer component of the Event Portal
  2. Double-click on the NYC Modern Taxi Co - Back Office Application Domain
  3. Click on the Applications tab
  4. In the Upper Right Corner, Click the Create button and select Create Application
  5. Create Application by setting values:
    1. Name: ProcessPayment
    2. Description: NONE
    3. Application Type: Standard
    4. Owners: Assign Yourself
    5. Tags: NONE
    6. Associate Events - Click the Manage link
      1. Select the Sub button next to the RideUpdated event
      2. Select the Pub button next to the PaymentCharged event
    1. Revision Comment: "Initial Creation of Application"
    2. Click Save
  6. You should now see the newly added application on the graph!

Step 4b: Design InvoiceSystem Application

Remember back to our use case... We have designed how we process payment but still have to deal with invoicing customers when the payment_status says to invoice. Therefore, our plan is to create an application that integrates with our invoicing system.

  1. Click into the Designer component of the Event Portal
  2. Double-Click on the NYC Modern Taxi Co - Back Office Application Domain
  3. Click on the Applications tab
  4. On the Upper Right Corner, Click the Create button and select Create Application
    1. Name: InvoiceSystem
    2. Description: NONE
    3. Application Type: Standard
    4. Owners: Assign Yourself
    5. Tags: NONE
    6. Associated Events - Click the Manage link
      1. Select the Sub button next to the PaymentCharged event
    1. Revision Comment: "Initial Creation of Application"
    2. Click Save
  5. You should now see the newly added application on the graph!

Reuse PaymentCharged Event

Getting reuse of your events is an important part of proving return on investment (ROI) and also enables other applications and teams to integrate with realtime data.

In this scenario we will act as though we are members of the "Ops" team (they are not as cool as us back office kids, but oh well). They have a use case that Payment charged events should go to the Rider Mobile Application. Lets make it happen!

  1. Click into the Designer component of the Event Portal
  2. Double-click on the NYC Modern Taxi Co - Ops Application Domain
  3. Double-click on the RIder Mobile Application
  4. On the Upper Right Corner, Click the Edit button
  5. Associated Events - Click the Manage link
    1. Select the Sub button next to the PaymentCharged event
  6. Revision Comment: "Updated to Satisfy JIRA-01245"
  7. Click Save

You should now see the relationship on the graph where we are subscribed to the PaymentCharged event and the dependency on the Back Office App domain!

💡 Know your Audience
The events which you have are used to enable real-time collaboration between systems and solve a problem for a specific industry and organization. These events are integrated into applications by software developers/engineers, but they are not all the same and can be decomposed into:

💡 Capture Business Point of View and Moment

💡 Technical Requirements

💡 Link to other References

💡 Provide Examples

💡 Terms of Use

💡 Tags

Update Documentation of PaymentCharged Event

Remember how we did not provide any description or tags for the Events and Applications we created before? Well, lets go in and follow our best practices fixing this. Lets enhance the documentation of the PaymentCharged Event

  1. Click into the Designer component of the Event Portal
  2. Double-click on the NYC Modern Taxi Co - Back Office Application Domain
  3. Double-click on the PaymentCharged Event in the graph
    1. Click on the Edit button
    2. Copy and Paste the following into the Description field:
Description of Business Moment

	Overview:

		The PaymentCharged Event exists in order to notify other systems that we have attempted to charge the passenger. There are also other states such as:
            accepted - customer credit card on file has been charged
            declined - customer credit card on file has been declined
            org - the customer is part of a B2B org and does not provide automated payment


Technical Requirements

Format: JSON
Security Level: PCI

Terms of Use

N/A


  1. Lets make it nicer to read by using bullets, bold, italics etc.
  2. Lets now also add Tags
    1. Click Add/Remove Tags
      1. Type PCI in the box and Select (Create a new tag) below.
      2. Optionally add other tags.
      3. Click Done
  3. The documentation should look something like: asyncapi_doc2
  4. Click Save

Update Documentation of ProcessPayment Application

Lets enhance the documentation of the ProcessPayment Application and put our Documentation Best Practices to work!

  1. Click into the Designer component of the Event Portal
  2. Double-click on the NYC Modern Taxi Co - Back Office Application Domain
  3. Double-click on the ProcessPayment Application in the graph
    1. Click on the Edit button
    2. Copy and Paste the following into the Description field:
Description of Business Capability

Overview:

    The ProcessPayment application solely exists in order to monitor for when Passenger Rides are completed such that final billing can be performed against the passengers credit card. Because this application will need to look up the passenger's billing information it is important that security be taken into account as it will need to be PCI compliant. 
    
    Upon successful payment, the application shall emit an event to signify that payment has happened.

Technical Requirements

  Java Version:  OpenJDK 11.0.4
  Spring Cloud Version:  Hoxton.SR8
  Number of Instances: 1
  Cloud: AWS us-east
  Security Level: PCI
  Event Broker Profile: Solace


Source Code Repository

  github repo



Terms of Use

  N/A


  1. Lets make it nicer to read by using bullets, bold, italics etc
  2. Lets add a hyperlink to the github repo that points to https://github.com
  3. Lets now also add Tags
    1. Click Add/Remove Tags
      1. Type PCI in the box and Select below.
      2. Optionally add other tags.
      3. Click Done
  4. The documentation should look something like: asyncapi_doc2
  5. Click Save

The AsyncAPI Initiative is an open source initiative that provides both the AsyncAPI specification to define your asynchronous APIs, and open source tools to enable developers to build and maintain an event-driven architecture.

The AsyncAPI Generator allows you to generate a wide variety of things from an AsyncAPI document depending on what template you choose. The latest list of templates can be found here

asyncapiSpecExample

asyncapiGeneratorTemplates

Install the AsyncAPI Generator

Now that we've defined the architecture for our use case in the Event Portal we're ready to write some code! But we don't want to have to write everything from scratch, so we're going to use the AsyncAPI Generator

In order to use the AsyncAPI Generator we first need to install the CLI.

If you have the prerequisites installed as defined earlier in the "What You'll Need" section you should be able to pop open your terminal and use the command below to install the CLI.

npm install -g @asyncapi/generator

Develop the ProcessPayment Microservice

🚕 🚖 🚕 🚖 🚕 🚖 🚕 🚖 🚕 🚖 🚕 🚖 🚕 🚖 🚕 On to developing the ProcessPayment App. As defined during the design sections of this codelab, we determined that this will be a microservice written using Java & Spring. We are going to use the Spring Cloud Stream framework to develop this microservice since it was created for the exact purpose of developing event-driven microservices. We'll also keep the business logic to a minimum to focus on the process of creating an event-driven microservice with AsyncAPI + Spring Cloud Stream and getting it running!

Generate the Code Skeleton

Open the NYC Modern Taxi Co - Back Office Application Domain in the Solace Event Portal, right-click on the ProcessPayment application, Choose AsyncAPI, Choose YAML and click Download

processPaymentAsyncapi

Direct Download of updated AsyncAPI document

If you want a pre-populated file with changes and additions, you can download using the following command.

curl -k -XGET https://raw.githubusercontent.com/Mrc0113/ep-design-workshop/main/ProcessPayment.yml -o ProcessPayment.yaml

After downloading, directly go to Code Generation step.

Manual updates to AsyncAPI document

Alternatively, you can follow the instruction and make changes to the AsyncAPI document.

Let's add a few of the template's configuration options to the downloaded AsyncAPI document.

✅ After adding those configuration options your channels section of the AsyncAPI document should look like the image below.

channels:
  'taxinyc/backoffice/payment/charged/v1/{payment_status}/{driver_id}/{passenger_id}':
    subscribe:
      x-scs-function-name: processPayment
      x-scs-destination: test/taxinyc/PaymentProcessorQueue
      message:
        $ref: '#/components/messages/PaymentCharged'
  ....
  ....
  'taxinyc/ops/ride/updated/v1/{ride_status}/{driver_id}/{passenger_id}/{current_latitude}/{current_longitude}':
    publish:
      x-scs-function-name: processPayment
      message:
        $ref: '#/components/messages/RideUpdated'

Code Generation

🚀 Our AsyncAPI document is now ready to generate the actual code so go over to your terminal and enter the command in the code snippet below.

Note the different pieces of the command:

ag -o ProcessPayment -p binder=solace -p dynamicType=header -p artifactId=ProcessPayment -p groupId=org.taxi.nyc -p javaPackage=org.taxi.nyc -p host=taxi.messaging.solace.cloud:55555 -p username=public-taxi-user -p password=iliketaxis -p msgVpn=nyc-modern-taxi ProcessPayment.yaml @asyncapi/java-spring-cloud-stream-template

✅ After running the command you should see output that ends with where you can find your generated files.

Done! ✨
Check out your shiny new generated files at /private/tmp/codelab/ProcessPayment.

Import and Explore the Generated Project

The generated project is a Maven project so head over to your IDE and import the project, so we can add our business logic. Once imported you should see something like the image below.
projectsetup2

A few notes on the project:

Updates to application.yml

There are certain updates required to the applicaion.yaml file to reflect destination names and subscriptions. You can use the following block of code and replace the content of application.yml file in the Spring project or manually make changes by following steps in the Changes to application.yml section.

spring:
  cloud:
    function:
      definition: processPayment
    stream:
      bindings:
        processPayment-out-0:
          destination: 'test/taxinyc/YOUR_NAME/backoffice/payment/charged/v1/accepted'
        processPayment-in-0:
          destination: test/taxinyc/PaymentProcessorQueue
      solace:
        bindings:
          processPayment-in-0:
            consumer:
              queueAdditionalSubscriptions: "taxinyc/ops/ride/updated/v1/dropoff/>"
      binders:
        solace-binder:
          type: solace
          environment:
            solace:
              java:
                host: 'taxi.messaging.solace.cloud:55555'
                msgVpn: nyc-modern-taxi
                clientUsername: public-taxi-user
                clientPassword: iliketaxis
logging:
  level:
    root: info
    org:
      springframework: info

After updating the file, directly go to Implement the Business Logic step.

Changes to application.yml

If you choose to make manual changes, please follow the instructions here.

Dynamic topics (topics with variables enclosed by {,} characters) is not supported by the AsyncAPI Code Generator in cloud stream bindings.

Let us make the following changes to the application.yml file.

Be sure to replace YOUR_UNIQUE_NAME with your name or some unique field; and remember it for later!. Because there are potentially multiple people using a shared broker participating in this codelab at the same time we need to make sure we publish to a unique topic.

Now we have the ProcessPayment application subscribing to a dynamic topic taxinyc/ops/ride/updated/v1/dropoff/> and publishing to test/taxinyc/YOUR_UNIQUE_NAME/backoffice/payment/charged/v1/accepted topic.

Implement the Business Logic

Obviously in the real world you'd have more complex business logic but for the sake of showing simplicity we're just going to log the RideUpdated events as they're received and create a new PaymentCharged event for each.

Open the Application.java file and modify the processPayment method to build and publish PaymentCharged event. Copy the following code block and replace the processPayment bean. Upon receiving a RideUpdated message, thie bean function constructs a PaymentCharged message and publishes to the unique topic (distinguished by your unique name).

@Bean
public Function<RideUpdated1, Message<PaymentCharged>> processPayment() {
  return rideUpdated -> {
    logger.info("Received Ride Updated Event:" + rideUpdated);

    // Process Payment
    PaymentCharged pc = new PaymentCharged();
    pc.setRideId(rideUpdated.getRideId());
    pc.setAmountCharged(rideUpdated.getMeterReading());
    pc.setPaymentStatus("accepted");
    pc.setPaymentChargedId(UUID.randomUUID().toString());
    pc.setInvoiceSystemId("PSG-" + RandomUtils.nextInt());
    pc.setInformationSource("ProcessPayment Microservice");
    pc.setTimestamp(Instant.now().toString());
    pc.setEntityType("Driver");
    
    org.taxi.nyc.RideUpdated1.Driver driver = rideUpdated.getDriver();
    pc.setDriver(new org.taxi.nyc.PaymentCharged.Driver(
            driver.getDriverId(), driver.getRating(), driver.getLastName(), 
            driver.getCarClass(), driver.getFirstName()));
    
    org.taxi.nyc.RideUpdated1.Passenger passenger = rideUpdated.getPassenger();
    pc.setPassenger(new org.taxi.nyc.PaymentCharged.Passenger(
              passenger.getPassengerId(), passenger.getRating(),
              passenger.getLastName(), passenger.getFirstName()
            ));

    logger.info("Created PaymentCharged Event:" + pc);
    
    String topic = "test/taxinyc/YOUR_UNIQUE_NAME/backoffice/payment/charged/v1/accepted";
    Message<PaymentCharged> message = MessageBuilder
        .withPayload(pc)
        .setHeader(BinderHeaders.TARGET_DESTINATION, topic)
        .build();

    return message;
    
  };
}

Be sure to replace YOUR_UNIQUE_NAME with your name or some unique field in the following line of code:

    String topic = "test/taxinyc/YOUR_UNIQUE_NAME/backoffice/payment/charged/v1/accepted";

That's it! The app development is complete.

Run the app!

Now that our app has been developed let's run it!

If your IDE has support for Spring Boot you can run it as a Spring Boot App.

Or run it from the terminal by navigating to the directory with the pom and running the following command in the terminal.

mvn clean spring-boot:run

Once running you should see that for each RideUpdated event that is received a PaymentCharged Event is created which is being published back out onto the broker for downstream apps to consume. The output should look something like the below.

2022-03-31 09:50:58.267  INFO 26107 --- [pool-4-thread-1] org.taxi.nyc.Application                 : 
Received Ride Updated Event:RideUpdated1 [ rideId: 056b4e73-60d6-4e90-bd29-0261d5824b5c heading: 35 latitude: 40.7888 passengerCount: 1 pointIdx: 545 informationSource: RideDispatcher speed: 18 driver: Driver [ driverId: 325 rating: 2.89 lastName: Ferro carClass: Coupe firstName: Florence ] passenger: Passenger [ passengerId: 66574939 rating: 4.39 lastName: Cast firstName: Sally ] meterIncrement: 0.0317431 longitude: -73.974224 timestamp: 2022-03-31T00:20:58.184-04:00 meterReading: 17.3 rideStatus: dropoff ]
2022-03-31 09:50:58.270  INFO 26107 --- [pool-4-thread-1] org.taxi.nyc.Application                 : 
Created PaymentCharged Event:PaymentCharged [ rideId: 056b4e73-60d6-4e90-bd29-0261d5824b5c entityType: Driver amountCharged: 17.3 driver: Driver [ driverId: 325 rating: 2.89 lastName: Ferro carClass: Coupe firstName: Florence ] paymentChargedId: 02917306-79ff-457c-b2bc-73a547bcfdbe passenger: Passenger [ passengerId: 66574939 rating: 4.39 lastName: Cast firstName: Sally ] paymentStatus: accepted invoiceSystemId: PSG-1656881013 informationSource: ProcessPayment Microservice timestamp: 2022-03-31T04:20:58.269Z ]

🤯🤯 The Microservice is now Running, connected to the Solace Event Broker and processing events! 🤯🤯

Develop the InvoiceSystem Node.js App

🚕 🚖 🚕 🚖 🚕 🚖 🚕 🚖 🚕 🚖 🚕 🚖 🚕 🚖 🚕 On to developing the InvoiceSystem application. We will be using the Node.js service that uses Hermes package to communicate with our event broker over MQTT. To do this we will leverage the Node.js AsyncAPI Generator Template to bootstrap our app creation. Note that MQTT is an open standard messaging protocol very popular in Internet of Things (IoT) world and is designed to be extremely lightweight and

Download AsyncAPI Document

Open the NYC Modern Taxi Co - Back Office Application Domain in the Solace Event Portal, right-click on the InvoiceSystem, Choose AsyncAPI, Choose YAML and click Download

invoiceSystemAsyncapi

Direct Download of updated AsyncAPI document

If you want a pre-populated file with changes and additions, you can download using the following command.

curl -k -XGET https://raw.githubusercontent.com/Mrc0113/ep-design-workshop/main/InvoiceSystem/asyncapi.yaml -o InvoiceSystem.yaml

After downloading, directly go to Code Generation step.

Manual updates to AsyncAPI document

Alternatively, you can follow the instruction and make changes to the AsyncAPI document.

    channels:
      'test/taxinyc/YOUR_UNIQUE_NAME/backoffice/payment/charged/v1/accepted':
        publish:
          operationId: receivePaymentCharged

Be sure to replace YOUR_UNIQUE_NAME with your name or some unique field in the following line of code:

  messages:
    PaymentCharged:
      name: PaymentCharged

servers:    
  production:     
    url: 'mqtt://taxi.messaging.solace.cloud:8883'
    protocol: mqtt

🚀 Our AsyncAPI document is now ready to generate the actual code so go over to your terminal and enter the command in the code snippet below.

Code Generation

Note the different pieces of the command:

ag InvoiceSystem.yaml @asyncapi/nodejs-template -o InvoiceSystem -p server=production

✅ After running the command you should see output that ends with where you can find your generated files.

Done! ✨
Check out your shiny new generated files at /private/tmp/codelab/InvoiceSystem.

Explore the Generated Project

The AsyncAPI Generator generated a nodejs project in the directory specified by the -o parameter so head over to your favorite Editor/IDE and open it up. Once opened you should see something like the image below.

nodejsProjectSetup

A few notes on the project:

Add the broker connection and protocol info

In the common.yml in the config directory:

Before coding our nodejs app let's go ahead and put our credentials in place.

  broker:
    mqtt:
      url: mqtts://taxi.messaging.solace.cloud:8883
      topics: ["test/taxinyc/YOUR_UNIQUE_NAME/backoffice/payment/charged/v1/accepted"]
      qos:
      protocol: mqtts
      retain:
      subscribe: true
      config:
        username: "public-taxi-user"
        password: "iliketaxis"

That's it! The app development is complete.

🚀🚀🚀 Was that simple enough for you!? 🚀🚀🚀

Run the app!

Now that our app has been developed let's run it!

# Go to the generated server

$ cd InvoiceSystem

# Build generated application

$ npm i

# Start server - Once running you should see PaymentCharged Events are received from the Broker. 
# The output should look something like the below.

$ npm start
> invoice-system@0.0.1 start /Users/xxxx/gitsolace/work/InvoiceSystem
> node src/api/index.js

 SUB  Subscribed to test/taxinyc/YOUR_UNIQUE_NAME/backoffice/payment/charged/v1/accepted
InvoiceSystem 0.0.1 is ready!

🔗  MQTT adapter is connected!
← test/taxinyc/YOUR_UNIQUE_NAME/backoffice/payment/charged/v1/accepted was received:
{
  driver: {
    rating: 2.24,
    driver_id: 77,
    last_name: 'Van Zandt',
    car_class: 'Coupe',
    first_name: 'Pablo'
  },
  passenger: {
    rating: 4.65,
    passenger_id: 93731728,
    last_name: 'Schlicht',
    first_name: 'Aaron'
  },
  timestamp: '2022-03-31T04:23:34.055Z',
  ride_id: 'effd0ef8-6358-4592-ac97-2be6173bfa4d',
  entity_type: 'Driver',
  amount_charged: 14.3,
  payment_charged_id: '553ef9a9-dd18-4112-b79d-970156d529d2',
  payment_status: 'accepted',
  invoice_system_id: 'PSG-1557019248',
  information_source: 'ProcessPayment Microservice'
}

🤯🤯 The Node.js app is now Running, connected to the Solace Event Broker receiving and logging events! 🤯🤯

You can create event driven applications in a wide variety of different options as shown here: ![APIs and Protocols] (img/Solace-PubSub-Platform-Diagram-1.png)

Generate Custom Code

Since the AsyncAPI Specification provides a machine-readable way to define your Asynchronous applications it allows for the creation of custom code generators. The easiest way to likely do this is to leverage the tooling that the AsyncAPI Initiative has already put in place and create a new template for the AsyncAPI Generator

✅ Event Driven Architecture does not have to be hard if you understand some key fundamentals and follow best practices.

✅ The Solace PubSub+ Event Portal is an excellent tool to design, visualize and document your Event-Driven Architecture, discover what events exist, collaborate with your team and kick start development via exporting of AsyncAPI documents.

✅ AsyncAPI Generator templates allow developers to consistently create event-driven applications by generating code skeletons that are pre-wired with the events and channels defined in the AsyncAPI documents.

Solly Image Caption

Thanks for participating in this codelab! Let us know what you thought in the Solace Community Forum! If you found any issues along the way we'd appreciate it if you'd raise them by clicking the Report a mistake button at the bottom left of this codelab.