swing and spring integration part 2

In Part 1, we saw that loosely coupled integration with legacy components is alot of work mostly because of the need to create scaffolding around error handling, method invocation, publishing and channel management (routing, filtering, bridging). In this blog, we want to assume that a loosely coupled model is the implementation approach and that spring integration will be used to implement it. Essentially, we are saying that the application context message publishing model is a good one and we want to put the simple event publishing mechanism that is provided in the application context today, with framework provided by spring integration.

Because we can assume that the components, where appropriate, will be loosely coupled and that spring integration is the implementation machinery, we can program interfaces, create subscription annotations and do many things that are one step closer to the application specific nature of the integration. Spring integration cannot make many assumptions about the application and its design goal of non-intrusiveness means that direct coupling to the integration library is not a design target. However, given our assumptions, we can increasing coupling by providing annotations and integration machinery that assumes spring integration is present. This allows us to look much more like eventbus.org’s event bus architecture but with the full configuration and integration capability of spring integration in the background if needed.

The eventbus.org’s design typically assumes a single event service (not strictly true but often true) that serves events on the swing thread. The assumption of a primary event service (the bus) means that the arguments to many publishing and subscription annotations and classes is greatly simplified—one of the reasons it feels lightweight. Do  we have a similar concept in spring integration? Well, yes and no. There is only a primary channel if one defines it as such. And, we’ll need to create subscription annotations that know how to resolve to it. Essentially, eventbus.org’s assumption on that main event service is much like defining a global variable that holds the event service and all short-cut methods refer to it by default. This is why you can write EventBus.publish(<your event object>) and have it work. Somewhere, in some object’s configuration, we have to specify a channel that subscriptions listen on and method publish to. Where should that be defined? Should we use a thread-local variable? How can spring be used efficiently to create loose coupling machinery only when needed (extreme laziness) so that we don’t have to specify a full configuration at execution start?

To accomplish these last goals, we’ll need some additional machinery to:

  1. Create channels, handlers and filters dynamically when needed. We need this capability to be efficient. We do not want to attached handlers to the main channel such that the main channel, which should be carrying a large message load, has to run through many handlers for each message. This can easily be programmed in spring integration. Instead, spring integration encourages the creation of channels that are filters and routings of another channel. So instead of creating message handlers that listen, for example, to a specific payload type directly on the main channel, we want to create another channel and route only messages with a specific payload type to the new channel from the main channel.
  2. A way to configure a single, preferred channel for publishing and subscribing. Currently, spring integration uses the application context to configure all channels. However, this does not allow dynamic creation of channels and clearly the designation of a primary channel may be relative to an application context.

Message payload type subscriptions

To implement subscribers to messages with a specific payload type, we can create a new channel, route messages from the main channel and attach handlers to the new channel that expect a certain payload type. While it is true that message payloads can be converted from one payload type to another, this is probably not going to be a key element of our implementation because we would expect message payloads to be highly diverse in our application. Here’s our approach:

  1. Create a new channel scope that allows the dynamic creation and resolution of new channels and routers based on payload type. We create a new scope because we want to create channels that handle specific payload types only once and reuse it as much as possible. Otherwise, we are creating a new channel each time unique to each use of the subscribing annotation. While creating an unique channel each time works, we need to create a shared channel and share it among all subscribers who want to receive events of a certain payload type.
  2. Create a new subscriber annotation that annotates methods to receive subscription events for. The default channel to listen for should be configurable.
  3. Create a simple locator object that allows us to publish to the primary channel so that it is eventually routed to the payload specific channel. This can be implemented different ways. For example, using a global locator service object that returns the configured channel. This can be used in straight java code without referring to an application context. Applications could also inject the context/bean factory with the primary channel, or just inject the primary channel. These last two approaches assumes that the injected object knows which channel inside the context/bean factory to use—but this may not be a burden in some cases.

As for dynamically creating channels and routers based on payload type, we'll create a scope that performs two functions. First, it manages the dynamically created objects and provides access to them in the application context. This works just like it does for a web application where the request object and conversations are stored in a scope within the application context, and second, it acts as a factory for creating channels and routers and having the routing listen to the desired channel. We could break this functionality apart of course and probably will in the future. The scope is below: We'll also need an annotation to annotate subscribers with: and a bean post processor to process the annotations on objects created or wired using the containing bean factory. There is more machinery involved but here's the core where the annotation processor works to configure the annotation and automatically create the wiring. The annotation processor (the bean post processor) needs the factory-aspect of the scope object to create the channels and routers: So now with a little config in our context: where the defaultChannel is the name of the primary channel defined elsewhere in our context. we can publish and subscribe as in the following: We'll discuss swing threading issues and more configuration support in another part of this series.


Popular posts from this blog

zio layers and framework integration

typescript and react types

dotty+scala.js+async: interesting options