spring integration and swing

At some point, your RCP application may be to complex to manage because of volume or complexity of managing observers when using the standard java swing observer pattern or java beans observer pattern with property change listeners or highly coupled listener lists.

Event.org has shown that it is possible to provide a looser coupling between components. The eventbus project is a good project and has a lightweight library that has a good following. In essence, it implements a queue internally to hold events that are distributed to event listeners. This is the same as what spring integration does although spring integration is designed to rely on the application context for configuration and creation as well as handles a fully general set of message bus functionality that is robust for many general application integration tasks. The event bus is conceived to help swing applications but is not as general.

Two questions arise:

  1. Does spring integration really help decouple components and integrate into legacy applications/code, and,
  2. Can spring integration also be used, perhaps with some small amount of coding support, similar to the event bus.

To help answer this question, I created a small project with legacy components and decided to integrate them into a simple swing UI. As you would expect, integrating with legacy components is challenging because you must take into account differences in:

  1. Invocation approaches,
  2. Error handling approaches, and,
  3. Object/service configuration approaches.

Together, these differences means that a variety of techniques are needed to perform legacy integration. While one could certainly use spring and DI heavily along with a few adapters, the project assumes that communication between components should be completely loosely coupled as much as possible. We also provide a cleaned up project that assumes spring integration from the start. One could find a middle ground as well, which is that it assumes well-written components that are friendly to loosely coupled systems and use spring integration to perform non-intrusive integration. However, if you assume from the start that you have a loosely integrated system and have a library that allows you to bridge to other messaging systems, you are probably making a pragmmattic design choice and balancing coding and configuration although clearly any messaging management should really be ideally external to the program code.

We’ll create a demonstration application that tests multiple scenarios of integration. It will be composed of the following components:

  • A Search component that allows search criteria entry and a reset button to reset the search criteria. The search component issues SearchEvents to search event listeners. The search event can be either a reset event or a search criteria event.
  • A Resuts component for displaying search results.
  • A Status component for displaying status of the application—a status bar  essentially.
  • A set of search services labeled A-D which provide different search strategies. Some of the search strategies always return results. Many of the strategies postfix their search results (from searching the domain model) with the search service name to allow you to identify which service returned the result.
  • A domain model that returns a static list of string objects. Used by all the search strategies for convenience.

Here’s a screen shot:

image

All of the scenarios use the legacy components defined in he org.test.integration.legacy package. The org.test.integration.common package defines some common elements needed across all of the scenarios, such as event message converters and the like. The org/test/integration/common/commonApplicationContext.xml defines many channels for the integration. The are described below. Note that using dots in the names of the channel is generally a bad idea because you cannot actually inject the channel by name from spring. We’ll change that in the next pass.

The application defines the following channels:
  • eventChannel: The main channel for the application.
  • eventChannel.search.event: Channel with any type of search event object on it.
  • eventChannel.search.event.searchRequest: Channel with only search event objects that have a criteria changed. Those with action=SearchAction.CRITERIA_CHANGED.
  • eventChannel.search.event.reset: Channel with only search event reset events. Those with action = SearchAction.RESET.
  • eventChannel.search.results: Channel for search results. Strings arrays are the only allowed datatype and a converter is attached to convert from a SearchBResult to a string array.
  • eventChannel.search.status: Channel for status messages. Only string messages are allowed.
  • eventChannel.search.event.searchRequest.criteria: A channel for only search requests with a criteria changed and whose payload is of type string. Search services that only take string arguments can be activated on this channel.
  • eventChannel.search.error: A channel that should have error strings in it. When any component encounters an error, it should use this channel to communicate the error. Each scenario may handle displaying errors to the user differently. Converters are registered for handling SearchServiceErrorEvent and ErrorMessage messages. The standard spring integration errorChannel is bridged to this channel.

For spring integration, each of the channels created handles a specific logical event. This allows us to integrate externally using XML directly into the legacy code. If the legacy code knew about event objects or message objects, in some more clever way, then we could route objects to them more easily. Because the legacy interfaces are not very thoughtful, we have to break apart logical events issued by other parts of the application, such as the Search component, into more basic components that can be directly injected or evoked on the legacy search services. Hence, the burden of legacy integration with fairly specific interfaces requires us to create more channels specific to the nature of the invocation—in essence we are pushing legacy integration and how the components work together into the spring integration layer—which is of course the point of spring integration.

A java Swing based example of using spring integration to connect components in a UI application assuming poorly written, poorly designed and in general poorly designed search components. The application does not assume that spring integration is being used to connect the components and that the components have no knowledge of how to wire together the events they produce and the consumers of those events. The components are decoupled from each other as well as agnostic about how to communicate and receive events. The classes are constructed as one normally would, to some degree, if not using any type of messaging system. They are glued together, however, using spring integration. Note that the structure of the classes and API are weak and not well engineered. Coupling is not well thought out. Different listeners are added in various places to create the flow of events.

An application designed from the start to be decoupled can be configured and written in a much simpler way than what is demonstrated in this package. In this application scenario, the search services have legacy interfaces, none of which match and hence, must be conformed, through configuration to produce results understandable by the Result component that displays search results. If the service interfaces all implement the same "search interface" or at the very least produce a commonly constructed "search result" object, integration would be significantly easier. In addition, there is no common way to provide status back to the user and this also must be conformed to a common approach. For convenience, it is assumed that spring is used to wire together the components into an swing UI application and make it easier to create the application but this type of wiring is not essential to the scenario. The application in this package is composed of classes that are not integration friendly nor particularly well designed.

For convenience, it is assumed that spring is used to wire together the components into an swing UI application and make it easier to create the application but this type of wiring is not essential to the scenario. The application in this package is composed of classes that are not integration friendly nor particularly well designed.

The objectives of the program include:

  • Demonstrate how a UI application can be wired together using spring integration with the assumption that there is no access to any of the components source code and that they came from different sources with different coding styles.
  • Demonstrate how spring integration can link together these components mostly through XML configuration versus say, dependency injection. We actually use dependency injection to create the application but this was a convenient choice versus assuming it has to be configured through spring DI. In other words, imagine that it is hard-wired together for basic application component composition but that the flow of events is where spring integration is being applied.
  • Demonstrate that tying together legacy classes in an application requires a significant effort and complexity in the configuration even with spring integration support. However, at least its possible to do so. You could, of course put together the legacy classes using traditional GUI application development techniques and coupling with lots of direct instantiation and observers that all know about each other component's structure, but that is not shown here.

Because some of the services, such as SearchServiceA and SearchServiceB, do not include error handling, a small error handling facility and channel have been provided. Some scenarios will have to wrap the interface with something (spring or custom code) so that exceptions are trapped and forwarded to the correct error channel. Hence, how errors are handled also speaks to the nature of how easily or hard legacy integration will be.

Scenario 1

The first scenario uses the legacy components, wraps only those search services that need error handling added with a small amount of glue code, but generally leaves all application integration up to spring integration. It does not try to be clever about coding the swing UI. The glue code code also have been written using AOP.

The search strategies are integrated in way described below. We are assuming that they are provided by 3rd parties and hence, do not have access to source to add annotations. In addition, we do not try to aggregate results because we want as-you-type searching and aggregation would introduce a timeout before results are sent. Also, the Results object acts as our aggregator through the addResults interface. We must use them as provided.
  • SearchServiceA: A XML service activator is used to have this channel and return a string array result directly to the results channel. It is wrapped in a subclass to allow exception error handling to be forwarded to the error channel.
  • SearchServiceB: Similar to SearchServiceA.
  • SearchServiceC: Listen to the eventChannel.search.event.searchRequest channel. Integrated through a XML service activator. The performSearch method already takes a SearchEvent parameter and returns a string array to the results channel.
  • SearchServiceD: Not integrated yet.

We have also created a SearchServiceActivator class that provides some status messaging using formatted messaging. It is not configured in the context although it easily could.

You’ll want to download the code and browse through the class comments. Many of the comments talk about trade-offs and decisions around legacy integration.

The next blog on this topic will be on Scenario 2, where we optimize the integration and talk about swing integration with loosely coupled components using spring integration.

Comments

Popular posts from this blog

quick note on scala.js, react hooks, monix, auth

zio environment and modules pattern: zio, scala.js, react, query management

user experience, scala.js, cats-effect, IO