Showing posts with label MVC. Show all posts
Showing posts with label MVC. Show all posts
Tuesday, September 3, 2013

Model-View-Controller (MVC) Done Right?

I was pleasantly reminded of how I, as a budding junior software engineer (a couple of years back), misunderstood MVC and application architecture, and how others may misunderstand this too. So, please allow me to explain my journey in understanding MVC better.

MVC and Application Architecture

Martin Fowler provides a good explanation of MVC in his Patterns of Enterprise Application Architecture.

When developing applications, one application architecture I'm usually exposed to is the layered architecture (also known as multi-layered, or multi-tiered). Each layer would usually be dependent on layers underneath it, but not above it. These layers provided abstraction (via interfaces), which is useful in tackling complex systems and allowing concurrent development.

A multilayered software architecture is a software architecture that uses many layers for allocating the different responsibilities of a software product.

Presentation Layer
Business Layer
Data Access (or Infrastructure) Layer

Missing Link Between MVC and Layered Architecture

So, I started thinking that MVC was mapped to layers like this:

LayerMVC Component
PresentationView
Business (or Domain)Controller
Data AccessModel

But that's the wrong way! A crucial point that I missed is that MVC is a presentation layer pattern. It is really intended for the user interface. So, what did I end up doing?

Because of this poor understanding of MVC, I ended up exposing domain entities as models to my views. This resulted in entities that exposed setters and getters, which made it difficult to implement invariants and DDD. This also resulted in business rules that were mixed with UI-related implementations. So, what is a better understanding of MVC?

LayerContent
PresentationMVC
Business (or Domain)Services / Domain Objects
Data AccessDAO / Repository Implementations / Gateways

As it was originally intended, MVC is found inside the presentation layer. It's important to note that the Model in MVC is not your Service Layer or the Domain Objects. It's the user interface's state (or View state).

If your application is simple enough, you could choose to have domain objects (usually entities) as your view state for simplicity. But please don't confuse MVC's Model with the Domain Objects.

If you find yourself having difficulty dealing with entities (or domain objects) in the presentation layer, perhaps you can consider converting domain objects to more portable DTOs that can be tailored to specific use cases (or specific UIs) and add an application service (typically using a facade pattern) that would handle the DTO-serializing responsibilities before exposing them to the presentation layer.

Sunday, May 6, 2012

MVCObject using Closure Library

I've recently been spending more time with JavaScript. I thought I'd make something like google.maps.MVCObject (from Google Maps JavaScript API) as a way for me to learn more about JavaScript and the Closure Library. So, here's what I've got, so far.

Understanding MVCObject

First off, here's how MVCObject behaves (at least, this is my understanding).

  1. When a property 'foo' of objA is bound to a property 'foo' of objB, the following happens:
    • Setting value x as property 'foo' on objB, results to objA.get('foo') returning the same value x.
    • Calling objA.get('foo') is equivalent to calling objB.get('foo'). In fact, objA.get('foo') returns the same object returned by objB.get('foo').
    • Setting value x as property 'foo' on objA is actually setting value x as property 'foo' on objB.
    • Calling objB.set('foo', x) notifies objA (and all objects bound to its 'foo' property) about 'foo_changed'. This effectively makes listening to objB's 'foo_changed' event equivalent to listening to objA's 'foo_changed' event.
  2. When a property 'bar' of objA is bound to a property 'foo' of objB, and property 'foo' of objB is bound to a property 'foo' of objC, the following happens:
    • Calling objA.set('bar') is equivalent to calling objB.set('foo'), which, in turn, is equivalent to calling objC.set('foo')
    • Calling objA.get('bar') is equivalent to calling objB.get('foo'), which, in turn, is equivalent to calling objC.get('foo')
    • ...You get the point.

This binding technique can save memory and provide better performance, since the object (or property value) is not duplicated. When bounded, Objects A and B actually point to the same value for their property 'foo'. So, if the object value happens to be a large object with several properties, then there's significant memory to be saved (since they point to the same object, and it is not duplicated).

MVCObject Tests

Second, here's how I started with some unit tests to help guide me in writing the MVCObject class with the Closure Library.

goog.require('goog.events');
goog.require('goog.testing.jsunit');
goog.require('example.MVCObject');

function testSetAndGet() {
  var objA = new example.MVCObject();
  objA.set('foo', 2);
  assertEquals(2, objA.get('foo'));
  assertEquals(2, objA.foo);
}

function testBindObjectAToObjectB() {
  var objA = new example.MVCObject();
  var objB = new example.MVCObject();
  objA.bindTo('foo', objB);
  objB.set('foo', 2);
  assertEquals(2, objA.get('foo'));
  assertEquals(objB.get('foo'), objA.get('foo'));
}

function testDispatchesChangedEventWhenPropertyIsSet() {
  var objA = new example.MVCObject();
  var called = false;
  goog.events.listenOnce(objA, 'foo_changed', function(event) {
    called = true;
  });
  objA.set('foo', 2);
  assertTrue(called);
}

function testUnbind() {
  var objA = new example.MVCObject();
  var objB = new example.MVCObject();
  objA.bindTo('foo', objB);
  objB.set('foo', 2);
  objA.unbind('foo');
  objB.set('foo', 3);
  assertEquals(2, objA.get('foo'));
}

MVCObject Implementation

Lastly, here's my implementation of MVCObject.

goog.provide('example.MVCObject');


goog.require('goog.events');
goog.require('goog.events.EventTarget');


/**
 * Base class implementing KVO (key-value object).
 *
 * @constructor
 * @extends {goog.events.EventTarget}
 */
example.MVCObject = function() {
  // Call super constructor
  goog.events.EventTarget.call(this);
};
goog.inherits(example.MVCObject, goog.events.EventTarget);


/**
 * @type {string}
 * @private
 */
example.MVCObject.EVENT_SUFFIX_ = '_changed';


/**
 * Returns the value of a property.
 *
 * @param {string} key the name of the property.
 * @return {*} the value of a property.
 */
example.MVCObject.prototype.get = function(key) {
  var binding = this.getBindings_()[key];
  if (binding) {
    // Use binding to get bounded object and return its value.
    return binding.target.get(binding.targetKey);
  } else {
    return this[key];
  }
};


/**
 * Sets a property.
 *
 * @param {string} key the name of the property.
 * @param {*} value the new value of the property.
 */
example.MVCObject.prototype.set = function(key, value) {
  var bindings = this.getBindings_();
  if (key in bindings) {
    // Use bindings[key] to get bounded object and set its value.
    var binding = bindings[key];
    binding.target.set(binding.targetKey, value);
  } else {
    this[key] = value;
    this.notify(key);
  }
};


/**
 * Sets a collection of key-value pairs.
 * @param {Object} values a collection of key-value pairs.
 */
example.MVCObject.prototype.setValues = function(values) {
  for (var key in values) {
    this.set(key, values[key]);
  }
};


/**
 * Lazily initialize bindings map.
 * @return {Object} the bindings map.
 * @private
 */
example.MVCObject.prototype.getBindings_ = function() {
  if (!this.bindings_) {
    this.bindings_ = {};
  }
  return this.bindings_;
};


/**
 * Adds a binding between the given property and a target property.
 *
 * @param {string} key the name of the property.
 * @param {example.MVCObject} target the target object.
 * @param {string} targetKey the name of the property on the target object.
 * @param {boolean=} opt_noNotify optional flag to indicate that no
 *     *_changed call-back shall be called.
 * @private
 */
example.MVCObject.prototype.addBinding_ = function(
    key, target, targetKey, opt_noNotify) {
  this.getBindings_()[key] = {
    target: target,
    targetKey: targetKey
  };
  if (!opt_noNotify) {
    this.notify(key);
  }
};


/**
 * Lazily initialize listeners map.
 * @return {Object} the listeners map.
 * @private
 */
example.MVCObject.prototype.getListeners_ = function() {
  if (!this.listeners_) {
    this.listeners_ = {};
  }
  return this.listeners_;
};


/**
 * Binds the property identified by 'key' to the specified target.
 *
 * @param {string} key the name of the property to be bound.
 * @param {example.MVCObject} target the object to bind to.
 * @param {string=} opt_targetKey the optional name of the property on the
 *     target, if different from the name of the property on the observer.
 * @param {boolean=} opt_noNotify optional flag to indicate that *_changed
 *     call-back shall not be called upon binding.
 */
example.MVCObject.prototype.bindTo = function(
    key, target, opt_targetKey, opt_noNotify) {
  opt_targetKey = opt_targetKey || key;
  this.unbind(key);
  this.getListeners_()[key] =
    goog.events.listen(
        target, opt_targetKey + example.MVCObject.EVENT_SUFFIX_,
        function() { this.notify(key); }, undefined, this);
  this.addBinding_(key, target, opt_targetKey, opt_noNotify);
};


/**
 * Removes a binding of a property from its current target. Un-binding
 * will set the unbound property to the current value. The object will
 * not be notified, as the value has not changed.
 *
 * @param {string} key the name of the property to be un-bound.
 */
example.MVCObject.prototype.unbind = function(key) {
  var listeners = this.getListeners_();
  var listenerKey = listeners[key];
  if (listenerKey) {
    goog.events.unlistenByKey(listenerKey);
    delete listeners[key];
    this[key] = this.get(key);
    delete this.getBindings_()[key];
  }
};


/**
 * Removes all bindings.
 */
example.MVCObject.prototype.unbindAll = function() {
  var listeners = this.getListeners_();
  for (key in listeners) {
    this.unbind(key);
  }
};


/**
 * Notify all observers of a change on this property. This notifies both
 * objects that are bound to the object's property as well as the object
 * that it is bound to.
 *
 * @param {string} key the name of the property that was changed.
 */
example.MVCObject.prototype.notify = function(key) {
  this.changed(key);
  // Dispatch *_changed event to all listeners
  this.dispatchEvent(key + example.MVCObject.EVENT_SUFFIX_);
};


/**
 * Generic handler for state changes. Override this in derived classes to
 * handle arbitrary state changes.
 *
 * @param {string} key the name of the property that changed.
 */
example.MVCObject.prototype.changed = function(key) {
};


/**
 * @override
 */
example.MVCObject.prototype.disposeInternal = function() {
  example.MVCObject.superClass_.disposeInternal.call(this);
  this.unbindAll();
};

I find Closure's way of declaring classes and methods to be very close to Java. It's event management makes user-defined custom events, and DOM/browser events, the same. It also provides a JsUnit-based testing framework. It's very rich in features.

It's making me think (or re-think) if there is value in having all these features under one roof. I mean, when I'm considering building a single-page app, I'd go look at backbone.js, which uses underscore.js. This may also mean using jQuery for DOM manipulation. I find that most (if not all) of the features of these three (3) JavaScript libraries can be found in Google's Closure Library (and possibly more). Will there be significant code savings when using one common JavaScript library?

All in all, I'm liking Closure. I'll spend more time learning it.