The concept of a distributed, event-driven architecture positions the ESB as central to everything, forming a key dependency with every other system. The ESB reduces system landscape complexity by replacing an n-to-n coupled system with 1-to-1 coupling between each system and the ESB. This critical coupling means that problems with the ESB have significant impacts, and changes may mean huge amounts of regression testing.
The primary goal of an event-driven architecture is to support system agility. The SOA and ESB architecture is intended to support change; however, the whole architecture is undermined if the testing effort is too high, too expensive and too unreliable.
Integration testing of event-driven architectures is difficult, and normally relies on a complex test ecosystem, requiring a specialised testing competency. It also traditionally requires a complex environment of systems to be maintained with appropriate test data. With lots of systems, traditional testing approaches fall short.
To effectively test an event-driven architecture, you need a testing framework that is equally comfortable generating events, consuming events and integrating with your systems to cause them to trigger events and to check that events received were processed correctly. This is where BWUnit excels.
Let's take as an example a CRM system that sends out an XML/JMS event whenever a customer's address is updated. We also have a billing system, which needs to be updated whenever a customer's address is changed. And we also have a shipping system, which likewise needs to be updated whenever a customer's address is changed. Here is what it might look like at a high level.
Let's focus first on the CRM. It can be configured so that changes to fields within the CRM cause SOAP/HTTP messages with the field values to be sent to designated external servers. It is connected to the ESB using a Service Broker (A.K.A.Channel Adapter), which transforms the messages into our Canonical Schema and publishes them on our ESB.
To test the CRM's integration with the ESB, we start with a simple human readable test scenario:
Given a contact "Jane Doe" at "24 Main St", "Anytown", "GA", "USA"
And a listener for "Address Change" events
When the address for "Jane Doe" is changed to "16 East Rd", "Anytown", "GA", "USA"
Then an "Address Change" event should be received for "Jane Doe" with "16 East Rd", "Anytown", "GA", "USA"
We will implement this as an integration test (for brevity in the article, we are skipping over unit and system tests for the service broker; however, they are what we would normally implement first), using a sub-process for each step.
The first step is to create the contact in the CRM with the details provided. The second step is to spawn a JMS topic subscriber. Step three will update the contact's details in the CRM. Finally step four will check that the address change event received by the spawned listener is in our canonical schema and has the correct content.
The billing system is basically a big database, which staff interact with using thick clients. It is also connected to the ESB with a service broker, which transforms address change events from our canonical schema into the billing system's schema and updates the database.
A test scenario for the billing system's integration with the ESB might be:
Given a customer "Jane Doe" at "24 Main St", "Anytown", "GA", "USA"
When an "Address Change" event is published for "Jane Doe" with "16 East Rd", "Anytown", "GA", "USA"
Then the address for "Jane Doe" should be "16 East Rd", "Anytown", "GA", "USA"
Again we will implement this scenario as an integration test, using a sub-process for each step.
Step one will create the customer in the billing system. Step two will send an address change event from our canonical schema to the ESB. Step three will check the address in the billing system has been correctly updated.
The shipping system is an old J2EE application with an XML-RPC API. Again, it connects to the ESB through a service broker, which transforms address change events from our canonical schema into the shipping system's schema and invokes the XML-RPC API. It has the same test scenario as the billing system and its integration test looks the same, but the step implementations will use the XML-RPC API instead.
Each of these tests expresses the same 1-to-1 coupling between each system and the ESB as illustrated in the following diagram:
We also want to perform a full integration test to ensure the decoupled systems work in harmony. Again we start with a test scenario:
Given a contact "Jane Doe" at "24 Main St", "Anytown", "GA", "USA" in CRM
And a customer "Jane Doe" at "24 Main St", "Anytown", "GA", "USA" in Billing
And a customer "Jane Doe" at "24 Main St", "Anytown", "GA", "USA" in Shipping
When the address for "Jane Doe" is changed to "16 East Rd", "Anytown", "GA", "USA" in CRM
Then the address for "Jane Doe" should be "16 East Rd", "Anytown", "GA", "USA" in Billing
And the address for "Jane Doe" should be "16 East Rd", "Anytown", "GA", "USA" in Shipping
If we look carefully at this test scenario, we will see that it is composed from steps that are used in the previous scenarios. Instead of re-implementing each of the steps for our end-to-end test, we are going to package our ESB integration test steps into libraries, so we can reuse them in our end-to-end test. Here is what our end-to-end test looks like:
And here is how the steps are reused:
By reusing our steps in this way, we greatly reduce the cost of implementing and maintaining our end-to-end tests. When one of the systems needs to be changed in such a way that the ESB integration test also needs to be updated, then our end-to-end test is automatically updated.
This is critical for enabling system agility and supporting change. In complex event-driven architectures, often the developers making changes to a system (and its integration to the ESB) are not aware of all of the upstream and downstream systems that interact with the system they are changing or how those systems behave. This makes them far more cautious and slower when developing their changes as they don't know what they might break. With end-to-end tests like the above in place, they can make changes faster and with confidence, knowing that the end-to-end tests will ensure that the systems continue to work as expected.
Also by embedding the test data in the tests as we have done above, we remove the need for setting up and managing appropriate test data in the test environments. With this requirement gone, we can then move to disposable virtual testing environments, which are set up and thrown away for each test run, rather than having a limited set of costly test environments that projects need to carefully schedule time in.
All of this is possible because of two of the key strengths of BWUnit:
Find out today how BWUnit can help you test your event-driven architecture and help you deliver on the architectural promises you've made.