Wednesday, October 28, 2009

Gateway



[ Team LiB ]









Gateway



An object that encapsulates access to an external system or resource.




Interesting software rarely lives in isolation. Even the purest object-oriented system often has to deal with things that aren't objects, such as relational database tables, CICS transactions, and XML data structures.


When accessing external resources like this, you'll usually get APIs for them. However, these APIs are naturally going to be somewhat complicated because they take the nature of the resource into account. Anyone who needs to understand a resource needs to understand its API�whether JDBC and SQL for relational databases or W3C or JDOM for XML. Not only does this make the software harder to understand, it also makes it much harder to change should you shift some data from a relational database to an XML message at some point in the future.


The answer is so common that it's hardly worth stating. Wrap all the special API code into a class whose interface looks like a regular object. Other objects access the resource through this Gateway, which translates the simple method calls into the appropriate specialized API.


How It Works


In reality this is a very simple wrapper pattern. Take the external resource. What does the application need to do with it? Create a simple API for your usage and use the Gateway to translate to the external source.


One of the key uses for a Gateway is as a good point at which to apply a Service Stub (504). You can often alter the design of the Gateway to make it easier to apply a Service Stub (504). Don't be afraid to do this�well placed Service Stubs (504) can make a system much easier to test and thus much easier to write.


Keep a Gateway as simple as you can. Focus on the essential roles of adapting the external service and providing a good point for stubbing. The Gateway should be as minimal as possible and yet able to handle these tasks. Any more complex logic should be in the Gateway's clients.


Often it's a good idea to use code generation to create Gateways. By defining the structure of the external resource, you can generate a Gateway class to wrap it. You might use relational metadata to create a wrapper class for a relational table, or an XML schema or DTD to generate code for a Gateway for XML. The resulting Gateways are dumb but they do the trick. Other objects can carry out more complicated manipulations.


Sometimes a good strategy is to build the Gateway in terms of more than one object. The obvious form is to use two objects: a back end and a front end. The back end acts as a minimal overlay to the external resource and doesn't simplify the resource's API at all. The front end then transforms the awkward API into a more convenient one for your application to use. This approach is good if the wrapping of the external service and the adaptation to your needs are reasonably complicated, because each responsibility is handled by a single class. Conversely, if the wrapping of the external service is simple, one class can handle that and any adaptation that's needed.



When to Use It


You should consider Gateway whenever you have an awkward interface to something that feels external. Rather than let the awkwardness spread through the whole system, use a Gateway to contain it. There's hardly any downside to making the Gateway, and the code elsewhere in the system becomes much easier to read.


Gateway usually makes a system easier to test by giving you a clear point at which to deploy Service Stubs (504). Even if the external system's interface is fine, a Gateway is useful as a first move in applying Service Stub (504).


A clear benefit of Gateway is that it also makes it easier for you to swap out one kind of resource for another. Any change in resources means that you only have to alter the Gateway class�the change doesn't ripple through the rest of the system. Gateway is a simple and powerful form of protected variation. In many cases reasoning about this flexibility is the focus of debate about using Gateway. However, don't forget that even if you don't think the resource is ever going to change, you can benefit from the simplicity and testability Gateway gives you.


When you have a couple of subsystems like this, another choice for decoupling them is a Mapper (473). However, Mapper (473) is more complicated than Gateway. As a result, I use Gateway for the majority of my external resource access.


I must admit that I've struggled a fair bit with whether to make this a new pattern as opposed to referencing existing patterns such as Facade and Adapter [Gang of Four]. I decided to separate it out from these other patterns because I think there's a useful distinction to be made.



  • While Facade simplifies a more complex API, it's usually done by the writer of the service for general use. A Gateway is written by the client for its particular use. In addition, a Facade always implies a different interface to what it's covering, whereas a Gateway may copy the wrapped facade entirely, being used for substitution or testing purposes.


  • Adapter alters an implementation's interface to match another interface you need to work with. With Gateway there usually isn't an existing interface, although you might use an adapter to map an implementation to a Gateway interface. In this case the adapter is part of the Gateway implementation.


  • Mediator usually separates multiple objects so that they don't know about each other but do know about the mediator. With a Gateway there are usually only two objects involved and the resource that's being wrapped doesn't know about the Gateway.




Example: A Gateway to a Proprietary Messaging Service (Java)


I was talking about this pattern with my colleague, Mike Rettig, and he described how he's used it to handle interfaces with Enterprise Application Integration (EAI) software. We decided that this would be a good inspiration for a Gateway example.


To keep things at the usual level of ludicrous simplicity, we'll build a gateway to an interface that just sends a message using the message service. The interface is just a single method.



int send(String messageType, Object[] args);

The first argument is a string indicating the type of the message; the second is the arguments of the message. The messaging system allows you to send any kind of message, so it needs a generic interface like this. When you configure the message system you specify the types of message the system will send and the number and types of arguments for them. Thus, we might configure the confirm message with the string "CNFRM" and have arguments for an ID number as a string, an integer amount, and a string for the ticker code. The messaging system checks the types of the arguments for us and generates an error if we send a wrong message or the right message with the wrong arguments.


This is laudable, and necessary, flexibility, but the generic interface is awkward to use because it isn't explicit. You can't tell by looking at the interface what the legal message types are or what arguments are needed for a certain message type. What we need instead is an interface with methods like this:



public void sendConfirmation(String orderID, int amount, String symbol);

That way if we want a domain object to send a message, it can do so like this:



class Order...

public void confirm() {
if (isValid()) Environment.getMessageGateway().sendConfirmation(id, amount, symbol);
}

Here the name of the method tells us what message we're sending, and the arguments are typed and given names. This is a much easier method to call than the generic method. It's the gateway's role to make a more convenient interface. It does mean, though, that every time we add or change a message type in the messaging system we need to change the gateway class, but we would have to change the calling code anyway. At least this way the compiler can help us find clients and check for errors.


There's another problem. When we get an error with this interface it tells us by giving us a return error code. Zero indicates success; anything else indicates failure, and different numbers indicate different errors. This is a natural way for a C programmer to work, but it isn't the way Java does things. In Java you throw an exception to indicate an error, so the Gateway's methods should throw exceptions rather than return error codes.


The full range of possible errors is something that we'll naturally ignore. I'll focus on just two: sending a message with an unknown message type and sending a message where one of the arguments is null. The return codes are defined in the messaging system's interface.



public static final int NULL_PARAMETER = -1;
public static final int UNKNOWN_MESSAGE_TYPE = -2;
public static final int SUCCESS = 0;

The two errors have a significant difference. The unknown message type error indicates an error in the gateway class; since any client is only calling a fully explicit method, clients should never generate this error. They might pass in a null, however, and thus see the null parameter error. This error isn't a checked exception since it indicates a programmer error�not something that you would write a specific handler for. The gateway could actually check for nulls itself, but if the messaging system is going to raise the same error it probably isn't worth it.


For these reasons the gateway has to both translate from the explicit interface to the generic interface and translate the return codes into exceptions.



class MessageGateway...

protected static final String CONFIRM = "CNFRM";
private MessageSender sender;
public void sendConfirmation(String orderID, int amount, String symbol) {
Object[] args = new Object[]{orderID, new Integer(amount), symbol};
send(CONFIRM, args);
}
private void send(String msg, Object[] args) {
int returnCode = doSend(msg, args);
if (returnCode == MessageSender.NULL_PARAMETER)
throw new NullPointerException("Null Parameter bassed for msg type: " + msg);
if (returnCode != MessageSender.SUCCESS)
throw new IllegalStateException(
"Unexpected error from messaging system #:" + returnCode);
}
protected int doSend(String msg, Object[] args) {
Assert.notNull(sender);
return sender.send(msg, args);
}

So far, it's hard to see the point of the doSend method, but it's there for another key role for a gateway�testing. We can test objects that use the gateway without the message-sending service being present. To do this we need to create a Service Stub (504). In this case the gateway stub is a subclass of the real gateway and overrides doSend.



class MessageGatewayStub...

protected int doSend(String messageType, Object[] args) {
int returnCode = isMessageValid(messageType, args);
if (returnCode == MessageSender.SUCCESS) {
messagesSent++;
}
return returnCode;
}
private int isMessageValid(String messageType, Object[] args) {
if (shouldFailAllMessages) return -999;
if (!legalMessageTypes().contains(messageType))
return MessageSender.UNKNOWN_MESSAGE_TYPE;
for (int i = 0; i < args.length; i++) {
Object arg = args[i];
if (arg == null) {
return MessageSender.NULL_PARAMETER;
}
}
return MessageSender.SUCCESS;
}
public static List legalMessageTypes() {
List result = new ArrayList();
result.add(CONFIRM);
return result;
}
private boolean shouldFailAllMessages = false;
public void failAllMessages() {
shouldFailAllMessages = true;
}
public int getNumberOfMessagesSent() {
return messagesSent;
}

Capturing the number of messages sent is a simple way of helping us test that the gateway works correctly with tests like these.



class GatewayTester...

public void testSendNullArg() {
try {
gate().sendConfirmation(null, 5, "US");
fail("Didn't detect null argument");
} catch (NullPointerException expected) {
}
assertEquals(0, gate().getNumberOfMessagesSent());
}
private MessageGatewayStub gate() {
return (MessageGatewayStub) Environment.getMessageGateway();
}
protected void setUp() throws Exception {
Environment.testInit();
}

You usually set up the Gateway so that classes can find it from a well-known place. Here I've used a static environment interface. You can switch between the real service and the stub at configuration time by using a Plugin (499), or you can have the test setup routines initialize the environment to use the Service Stub (504).


In this case I've used a subclass of the gateway to stub the messaging service. Another route is to subclass (or reimplement) the service itself. For testing you connect the gateway to the sending Service Stub (504); it works if reimplementation of the service isn't too difficult. You always have the choice of stubbing the service or stubbing the gateway. In some cases it's even useful to stub both, using the stubbed gateway for testing clients of the gateway and the stubbed service to test the gateway itself.








    [ Team LiB ]



    No comments:

    Post a Comment