• No results found

Domain-driven Messaging Gateways

In document Spring Integration in Action (Page 128-131)

Getting down to business

91Domain-driven transformation

5.4 Domain-driven Messaging Gateways

The noninvasive interceptor-based approach described in the previous section is a powerful yet simple way to send messages based on actions that occur within the busi- ness service layer. But interception is only really useful for reactive use cases in which the publication of a message is considered a by-product of some other primary action.

98 CHAPTER 5 Getting down to business

There are other equally valid use cases in which publishing a message is itself the pri- mary action, and for those cases, a more proactive technique fits well.

Such use cases correspond to the traditional event-driven programming model. Suppose you want to send a general notification that a flight status has been updated. Several parties might be interested in that event. One way to accomplish this is to pub- lish the event to each interested party. The following listing demonstrates a class that accomplishes this in an intuitive but less-than-ideal way.

package siia.business;

public class FlightStatusNotificationPublisher { private MailSender mailSender;

private JmsTemplate jmsTemplate;

public FlightStatusNotificationPublisher(MailSender mailSender, JmsTemplate jmsTemplate) { this.mailSender = mailSender;

this.jmsTemplate = jmsTemplate; }

public void publishNotification(FlightStatusEvent event) {

SimpleMailMessage mailMessage = null; // TODO: create mail Message this.mailSender.send(mailMessage);

this.jmsTemplate.convertAndSend(event); }

}

Why is this example less than ideal? There are a number of reasons. First, consider what you’d need to do if you encountered a new requirement to add another destination for these events, such as the local filesystem. You’d need to refactor the code to handle that. The revised implementation might look something like the following listing.

public class FlightStatusNotificationPublisher { private MailSender mailSender;

private JmsTemplate jmsTemplate;

private File directory;

public FlightStatusNotificationPublisher(MailSender mailSender, JmsTemplate jmsTemplate, File directory) {

this.mailSender = mailSender; this.jmsTemplate = jmsTemplate;

this.directory = directory;

}

public void publishNotification(FlightStatusEvent event) {

SimpleMailMessage mailMessage = null; // TODO: create mail Message this.mailSender.send(mailMessage);

this.jmsTemplate.convertAndSend(event);

Listing 5.5 Publishing event notifications

99

Domain-driven Messaging Gateways this.writeFile(event);

}

private void writeFile(FlightStatusEvent event) { // convert the event to a String

// generate a name for the target File

// write the content to the target File within the directory }

}

Listing 5.6 demonstrates the increased complexity as you try to meet the demands of too many requirements within a single component. Imagine trying to test the publishNotification method in this code. With three different notification targets being handled by a single method invocation, it would be difficult to completely iso- late each one in a test. That’s a pretty good indicator that you’re violating the separa- tion of concerns principle.

We left out the full implementation details of the writeFile method but purpose- fully provided three comments to describe what such an implementation entails. As you’ll see in chapter 11, each step can even be addressed by separate components or strategies when using Spring Integration’s file adapters. As for mail sending and JMS publishing, the preceding examples assume use of the corresponding support in Spring. In both cases, there is still a dependency on the mailSender and jmsTemplate objects in the code. As you’ll learn later in the book, Spring Integration builds on such support classes provided by the underlying Spring Framework, but with its more generic messaging model, it allows for an even greater degree of separation of con- cerns. The goal is to remove all awareness of such infrastructure from code in favor of a declarative, configuration-driven model.

For now, don’t worry about the details of these channel adapters. As we said, you’ll have plenty of exposure in upcoming chapters. The point here is to appreciate the configuration-driven nature and to see how the messaging gateway pattern can be sup- ported by a simple proxy. Consider the following configuration excerpt:

<gateway default-request-channel="flightStatusNotifications" service-interface="siia.business.FlightStatusNotificationPublisher"/> <object-to-string-transformer input-channel="flightStatusNotifications" output-channel="flightStatusStrings"/> <publish-subscribe-channel id="flightStatusStrings"/> <mail:outbound-channel-adapter channel="flightStatusStrings" mail-sender="mailSender"/> <jms:outbound-channel-adapter channel="flightStatusStrings" destination="flightStatusQueue"/> <file:outbound-channel-adapter channel="flightStatusStrings" directory="/siia/flightStatus/"/>

Even without knowing any details about the mail, JMS, and file adapters, this configu- ration should be self-explanatory. The main idea is that multiple adapters are sub- scribed to a channel, and as a result, the producer of flight status notifications doesn’t need to know anything about the individual subscribers. The producer only needs to

100 CHAPTER 5 Getting down to business

know about the single channel. Likewise, this configuration-driven approach is extensible. If you need to add or remove subscribers, it’s a matter of inserting or delet- ing the corresponding element. As far as transformation is concerned, it’s also treated as a separate concern. As you can see, the object-to-string-transformer element precedes the channel, since it generates a payload type that all of the adapters can accept. If you need to transform to different types for each of the adapters, you can add transformers after the common channel instead. And, of course, if you have cus- tom transformation requirements, it’s just as easy to provide a reference to your own transformer implementation. You’ll learn more about each of these adapters and their associated transformers in upcoming chapters.

The one element we do want to discuss in some detail here is the gateway. As you can see, it declares the fully qualified name of an interface. The result of that element is a generated proxy. If you’ve ever worked with Spring’s support for remoting via Remote Method Invocation (RMI) or Spring’s own HTTP invoker mechanism, this concept will be familiar. The element triggers the registration of a Spring Factory- Bean that produces a dynamic implementation of the declared interface. In this case, the proxy is backed by the supporting code in Spring Integration that maps the argu- ments to a message and then publishes that message to the referenced channel.

Earlier we discussed the testing aspects of the non-ideal implementation as an indi- cator that the code was fragile. With that implementation, it was difficult to isolate the various notification subscribers’ code for testing purposes. Now the story is much dif- ferent. For one thing, relying on the framework considerably reduces the amount of in-house code to be tested. In fact, the only code from the previous example that isn’t part of the framework is the interface. This means that all responsibility for testing the internal functionality of the subscription side belongs to the Spring Integration devel- opment team. You’d likely want to have some level of integration testing to verify that the mail sender, JMS queue, and filesystem directory are configured properly. In terms of unit testing, though, the focus can shift to the publishing side. Because that’s now reduced to the invocation of a method on an interface, it should be easy to build a battery of tests using traditional mock

or stub techniques.

In document Spring Integration in Action (Page 128-131)