By Peter Bell

Reconsidering Controllers - Part 1

I've spend most of my time with my in-house framework focusing on the model as that is where the heavy lifting goes on. I've used a base object controller to good effect so I can just make parameterized calls to generic form process, list and other base methods to cut down substantially on my controller code, but I still have front end "features" that act as a facade to this and right now they are still written in a 3gl (ColdFusion). My goal is to have more configuration and less (no?) coding in my controllers, so I'm trying to figure out just the right way to break this down . . .

I guess the first question is "what problems am I trying to solve"? To answer that I have to describe a little bit about my current framework structure.

Current Structure
I use a pseudo-page controller where each page has a feature which can be parameterized within the page so I can do things like have a store.html pointing to the home page of the store and a specials.html pointing to the equivalent of store.html?CatID=71 if category 71 is the "specials" category. It also means I can allow non-technical users to easily manage the functionality of different parts of the site (within the limits of the configuration options provided by each feature). This works really well for me as it completely decouples the orthogonal concerns of URLs and the content/functionality they access.

In terms of the API to my model, I have a service layer with a service class for every business object, so if you want to get a list of Users or to add a new Product you'd call UserService.getByFilter() or ProductService.add(). Of course, the service classes shouldn't know anything about forms or URL scopes as they need to be usable by a range of different front ends, so I need some way of loading a form scope into a bean or search parameters from a form or URL into a well formed request and to take page, order and filter information to provide a well formed getByFilter() call against the service class. Given that such tasks are generally the same across objects (the parameters for describing a list of Users or Products are the same, similarly the differences between editing an Article or a Product Attribute can also be described through parameterization), I use a base object controller to implement all of the common classes of action (list, add, edit, delete, display form, detail, import, export, report, publish, approve, etc.) and allow for "virtual methods" where you could (for instance) define a Register method for a User object in terms of a add() action, parameterizing it with the appropriate properties, validations, etc. so a line of XML containing the parameters would act as if you really had created a UserController.register() method (and is language independent).

With all this, I find that my "feature" controllers which act as a facade to the object controllers are extremely simple, so I'm looking for a DSL to describe the functionality so I can get rid of the code and replace it with configuration. The issue I'm having is what exact configurations and parameterizations to put where as it is a trade off depending on how you want to provide your reuse options and balance flexibility with simplicity. I'm also trying to decide whether to keep the object controller concept or to merge both my features and object controllers into a single layer. The thing I like about the idea of object controllers is that it would be possible to overload a given method (let's say list) for a particular object, but I haven't found myself doing that (hmm, a little YAGNI might have been in order) so I'm wondering if I could dump the object controllers and just have a base controller with "object" just being one of the parameters set in a given "virtual method".

For a while I considered getting rid of the "features", just requiring every page to be associated with a given object controller, but while that covers some use cases (especially when you make one controller call per content area so products in the middle of the page and a list of testimonials on the right are handled by two independent controller calls so you don't have to worry about tying them together), but there are plenty of cases where a feature relates to multiple objects (Catalog: category and product) and while you could take a naked objects like approach of considering there to be a primary object for each page and just navigating the object tree, it doesn't always work. If I navigate through a category to view a product detail I could think of that as Category=7&product=12&action=productdetail and could call the CategoryService to "get composed product", but when you access products via a search they don't always have a primary category and there are plenty of other use cases where an object controller isn't the most elegant solution for describing the functionality required by a content area on a given page.

Some Sample Cases
Now that I've thoroughly confused myself, I'm going to try looking at some sample requirements. As per the advice on building up DSLs, alternating between top down and bottom up can be useful, so I'm going to start by describing some use cases in "human" language and I'll see if any commonalities and variabilities emerge. If that doesn't work, I'll go back to some of my controller code to see if I can see commonalities and variabilities from the bottom up.

Feature Action Object Description
Page Display Display Page Get and display the HTML for this page (Page.HTML)
Catalog Category View Category Get and display category (including composed subcategories and products within the category). Template may be category specific
Catalog Category Product View Category Get and display category (maybe title) and single product within it. Template may be product specific
Cart View Cart Get and display the current site users cart
Catalog Search Product Get and display list of products matching the criteria
Admin Select Object Application Get a list of administratable objects and display them
Event Calendar Month View Event Get a list of all events in the selected month and display them in a calendar view
Contact Us Display Form n/a Just display the contact us form

So, what are some of the patterns coming out of the (very simple) data so far? Some times we just need to display a screen template and don't need to get any data from the service layer at all (that is why I put n/a for not applicable for the object service with those). Often we need to run a single method call against a single object service (which handles returning composed objects if you need a category including its products or an insured party and their addresses and dependents) and display a screen. Sometimes the screen is a function of the object instance (different categories or products may use different display templates - think an "out of stock" product template or different category layotus depending on the types of products that they contain). The commonality between these cases is that they just need a single actions and are queries rather than commands - they don't affect state (other than peripherally in terms of logging or view history or similar things).

So, if we only ever needed to look at things, we might be able to have a simple language that said something like:

action: name="x" object="y" method="z"
/action

with support for n parameters for parameterizing the method calls. For example, Catalog.CategoryView might look something like:

action: name="CategoryView" object="Category" method="getByID"
param: name="IDList" value="%Input.CatID%"
param: name="PropertyNameList" value="Title,Description,MainImage,SubCategories,Products"
/action

Category.CategoryProductView might be:

action: name="CategoryProductView" object="Category" method="getCategoryProduct"
param: name="IDList" value="%Input.CatID%"
param: name="IDList" value="%Input.ProductID%"
param: name="PropertyNameList" value="Title"
param: name="ProductPropertyNameList" value="*"
/action

action: name="NewEventList" object="Event" method="getByFilter"
param: name="Filter" value="EventType = 'new'"
param: name="PropertyNameList" value="StartDate,EndDate,Title"
param: name="Order" value="StartDate DESC"
/action

action: name="MyEventList" object="Event" method="getByFilter"
param: name="Filter" value="EventUserID = '%SiteUser.ID%'"
param: name="PropertyNameList" value="StartDate,EndDate,Title"
param: name="Order" value="StartDate DESC"
/action

(I'm not saying these APIs are good, btw, I'm just throwing out quickly conceived, plausible examples to find commonalities.)

Note that in the second case, getCategoryProduct encapsulates all the logic for returning a single object composed within its parent category because it is a function that may well be required by other front ends so the implementation should be left to the service method - not the controller. Also note it is against the category service as while I primarily want to display the product, I'm displaying it within the context of a specific category (and may need to display the title of that category at the top of the screen) so that seems the most logical approach. It also means that if I choose to compose a collection of cross sells, I'll be able to make them category as well as product specific if I so want.

Also note that in some cases the params are dynamic (input.CatID and input.ProductID where input is the merged form and URL scopes) and in some other cases, a param includes both static and dynamic content - param: name="Filter" value="EventUserID = '%SiteUser.ID%'". There obviously needs to be support in the language for exposing a number of common scopes for dynamically evaluating parameters based on URL, form, site user, page and maybe some other scopes.

Of course, life is never quite that simple! In my next posting I'll look at implementing commands . . .

Thoughts?

Comments
BlogCFC was created by Raymond Camden. This blog is running version 5.005.