Saturday, January 30, 2016

The Bridge Pattern with Websockets and HTTP


Design patterns don't tell us how to develop systems. Patterns are names applied to how we already develop. Knowing patterns well won't help us to implement them better in the future—we are already doing that very fine, thank you very much. Knowing a pattern is understanding its consequences and puts us in a better position to minimize or take advantage of those consequences.

That's all fine—if you recognize the pattern. When I first started playing with it, I could not think of an example in which I had used the bridge pattern. It took me some brain digging, but I think I finally excavated a real-world example of it.

Consider, if you will, a browser application that normally communicates with a server over websockets, but has to switch to plain-old HTTP under certain circumstances. The reason for switching might be an older client, a low-memory browser, or simply that this part of the application needs to talk to an HTTP-only server.

Whatever the reason, the web application should not change when the communication implementation changes. If the application is a simple form, then the web form and backing code should never change when switching between HTTP and websockets:



I need to suss out better names for the actors in this play, but I start by calling the web form a "messenger" and mechanism for talking "communication." They are kind of the same thing, so I need to work on that, but it will do for a start. The Messenger abstraction will require a Communication implementation of some kind and will need to know how to send messages with that communication channel. Expressed in Dart, that looks like:
abstract class Messenger {
  Communicator comm;
  Messenger(this.comm);
  void send();
}
The refined abstraction is a web form messenger that gets the message to send from a text element:
class FormMessenger extends Messenger {
  Element _messageElement;
  FormMessenger(this._messageElement) : super(new HttpCommunicator());

  void send() {
    comm.save(message);
  }

  String get message => _messageElement.value;
}
As can be seen from the send() method, the Communication class needs to support a save() method in order to save the message on the backend:
abstract class Communicator {
  void save(String message);
}
The HttpCommunicator is simple enough, thanks to the HttpRequest.postFormData() method:
class HttpCommunicator implements Communicator {
  void save(message) {
    HttpRequest.postFormData('/save', {'message': message});
  }
}
The second implementor, the WebsocketCommunicator class, takes a little more setup for websockets, but is still pretty straight forward:
class WebSocketCommunicator implements Communicator {
  WebSocket _socket;
  WebSocketCommunicator() { _startSocket(); }

  _startSocket() async {
    _socket = new WebSocket('ws://localhost:4040/ws');

    var _c = new Completer();
    _socket.onOpen.listen((_){ _c.complete(); });
    await _c.future;
  }

  void save(message) {
    _socket.send(message);
  }
}
And that pretty much does it. Aside from some better naming, this feels like a fairly accessible example.

One of the implications of the bridge pattern is that a code decision needs to be made as to where to assign the implementor. For a fist pass, I listen to the two radio buttons in the form and, based on which is selected, I assign a different Communicator:
  queryAll('[name=implementor]').
    onChange.
    listen((e) {
      var input = e.target;
      if (!input.checked) return;

      if (input.value == 'http')
        message.comm = new HttpCommunicator();
      else
        message.comm = new WebSocketCommunicator();
    });
With that, I can switch between implementors and send messages to the server however I like:
$ ./bin/server.dart
[HTTP] message=A+web+form+message%21
[WebSocket] A web form message!
[HTTP] message=A+web+form+message%21
[HTTP] message=A+web+form+message%21
[HTTP] message=A+web+form+message%21
Nice! Now all that is left is to come up with some better names.

Full code for the frontend and backend is located at: https://github.com/eee-c/design-patterns-in-dart/tree/master/bridge.


Day #80

No comments:

Post a Comment