Thursday, March 1, 2012

Inheritance and Composition: There and back again

I recently got an application request to provide a web interface for a mobile app. The would serve as a demonstration for the mobile app's features. Both apps connect to a cloud datastore that provides a REST API for content.


I've gotten to the point that, whenever I start a new app, I try to think pretty heavily about what could change. I could've hacked and slashed through it, but I wanted to take a few moments to follow the basic Gang of Four design principles.



  • Encapsulate what varies

  • Program to an interface, not an implementation


As cloud based services are still new, and a relatively unstable market, my immediate thought was:what if we switch from one to another? Should the entire app need to be re-written? I say No.


Consequently, I wanted to encapsulate the variation of datastore. Ideally, my Domain model could remain as free as possible from specific provider concerns. It should focus on just the application function interactions.


I decided to build the app quickly in a framework that I'm comfortable with and enjoy, Grails. Grails boasts an advanced application architecture that really takes the concepts of Fowler's Patterns of Enterprise Application Architecture such as a Service Layer (something Rails does not seem to have out of the box, though it supports something kind of similar in "Helpers"), Data Mapper, Domain Model, Template View, Front Controller, and many other pieces that enable a competent, enterprise level senior developer to comfortably focus on building a god application from solid software engineering principles without having to build everything from scratch.


My initial approach was to handle this encapsulation of concepts in the Service Layer. The controller should only know about a Facade DatastoreServicewhich internally delegates to the application provider API.



Seeking to follow the concepts of Clean Boundaries from Uncle Bob Martin's Clean Code , I defined my own application level interface for the datastore. I then define a subinterface that extends that interface for the specific application provider, augmenting it with methods to encapsulate the REST API calls. An abstract class implements invariants of that interface and provides an extension point for the implementing service.



At this point,I'm starting to get worried. I seem to be adding a lot of complexity to the application for a very basic desire. Especially when further implementation of functionality made me need to store the 3rd party library's unique indentifier for my subsequent querying. As I tested, built, and refactored, I found my application as internally at odds about how to do this as I was.



On one hand I've used an inheritance hierarchy to store the 3rd party API's object id in my Domain Model. On the other, I have such a slim Facade over the 3rd party API in the service layer that it's just noise. I have two options:



  1. Remove the Datastore service and have the DemoController directly talk to the ParseService.

  2. Refactor the Domain Model to tease apart the 3rd party API id.


At first, I wasn't comfortable with the concept of option 2. It seemed conceptually strange. A Zapper IS A ParseEntity, and so is a ZapCard. Until I realized that what I'm saying, within the concept of my application, is that Zapper HAS A ParseIdentity. In effect, I'm saying that my Domain Model exists outside the context of the datastore,but has a representation within it.


This is conceptually interesting. We are used to thinking about the world in terms of how things are. I am a Person. I am also a Student. All Students are Persons, hence a Student IS A Person. But I'm not just a Student. I'm also an Entrepreneur, Engineer, Activist...How can I reconcile all of this without multiple inheritance?


Instead, what if I am a Person, who HAS A Student identity, HAS A Engineeridentity, etc etc. Then I can have a single canonical representation, and assume different roles in different contexts. When I have to activate pieces from a different context (such as drawing upon my Student.study() within an Engineer context, this process is simplified. Instead of having to cast myself into a different role, my fundamental representation is invariant (Person) and calls for identity are delegated to the appropriate concept. Or, to flip this in reverse, when I am a Student I need to access information that I have as a Person, I access it from my internal Person representation. Like a Russian Egg. Hence,prototypal inheritance built from composition.

No comments:

Post a Comment