SoC principles for presentation layers

As software developers are all familiar with the design principle of separating concerns (SoC) in our applications design. By decoupling logical layers of code or removing inner dependencies we reduce code duplication, improve the maintainability of the code as well improving the test-ability of our code.   Where I see many development teams struggle though, is in the enforcement of this pattern through all layers, especially the presentation layer.

When applying SoC principle to a project the goal is encapsulation of code often referred to as concerns. We want to avoid where possible dependencies or the bleeding of concerns across layers. Through this layered design we can define our application to have clearly defined concerns such as the presentation layer, application/business layer, data access, infrastructure, and domain each with a clearly defined interface. Each layer if designed correctly should be able to stand on it's own. What I mean by that is in development, if a developer needs to work on the application layer they should be able to do just that; launch that project(s), make the necessary changes, and run unit tests without the need to launch or run the other layers. This is often referred to as modularity of the application.

So how does this apply to the presentation layer? Well first, we need to recognize that the presentation layer is more than just a view. The presentation layer is the interface that the user will use to interact with our application. Notice I said "the interface that the user will use"? Part of encapsulation in our design pattern is for the developer to create a well defined interface for the concern they are working in. The same rule applies in the presentation layer.

For this article, I'll speak to this pattern from the implementation of a web application using the model view controller pattern (MVC). When designing a web application there are a number of incredible frameworks available that promote the usage of an MVC/MVVM pattern. This article is not intended to be a tutorial on any one framework but more on the patterns and how they apply.

The View

The View is the most outward facing portion of an MVC model. It is the presentation or view the end user interacts with. When thinking of the view the view should be like a picture frame that overlays your data. It should not be tightly coupled to any other layer. In other words, it should be dumb or anemic. It's here that dev teams often get into trouble here they used libraries libraries designed for DOM manipulation to reference from the controller these elements (inputs, buttons, etc) to perform actions or marshal data to the API. Why is this a problem? The moment we make the view aware of the controller or the controller aware of the view we have created a dependency between the two. From this dependency we now have fragile code, changing an element (or worse a view in total) breaks the application. Remember the picture frame scenario? If you want to change from a wooden frame to a steel picture frame which is easier: the picture that was laid in the frame or the one that had glue run around the edges to keep it in place?

The view should be just that a view, it should contain no business logic, its sole purpose is to display data and manipulate the DOM (period, nothing more). No other part of the MVC model (model or controller) should ever care or be concerned with the DOM or changing it. While the view can attach events in the DOM such as onClick() events; the controller is responsible for handling these events. The controller also serves as the mediator between the model and view. The model's responsible for the transformation of data, issuance of calls to the API or any business/application logic needed within the web client.

MVC anti-pattern examples

When working with MVC many struggle with the role of the controller and end up intermingling the responsibilities of each concern. Here are a few examples:

As previously stated, the controller and model should have no knowledge or concern of the DOM. Yet you need to get the data value from an input field into the model or you need to listen for the click event of a button. This leads to some common bad practices:

  • Controllers using selectors to look up elements in the view to handle events
  • Controllers manipulating the DOM based upon an event
  • Binding within the controllers input elements to be marshaled off to the model
  • Within the model selectors looking for DOM elements to get their data values

Through these examples we now have a tightly coupled view, controller and model resulting in fragile code, little to no test-ability and the addition of technical debt if there is ever a need to replace or revisit the view with a different or new view.

Implementation

There are a number of existing frameworks available today that allow us to implement an MVC approach to our application. For example Angular and AngularJS support this model right out of the box. Depending upon the scope of your project you may determine that the services in angular could server as the model. If you prefer to keep the services more pure you could define within your project a model module that is injected into the services as needed.

The latter is my preferred approach and that is driven by the encapsulation concept. Reduce and reuse code as much as possible. If I create a model that allows me to not only defines the structure of the object I am working with, but also allows me to transform the data and return the data in different formats. That sounds like a good start to a clean design. Here's a watered down version of what I am describing.

Using angular I have a service which is called from my controller based upon an event that was triggered from the view. This service is then used to create the object/request and make that request to our API endpoint. The response is then returned to the view.

This is a very basic approach and can work in many situations. Where this becomes problematic is we are placing a lot of responsibility within the service. Is that necessarily bad? I think it depends upon the scope of the application and the need for re-usable code or possibly testability. As I mentioned above, adding to the existing project a model layer would allow us to off-load to the model all the domain logic and rules for the object we are working with.

We now have extracted work or business rules from the service and placed this logic into a pseudo domain model for the client. By doing this we gain the following: first, the service becomes more generic. It doesn't need to be concerned with one view or entity type. It no longer needs to be concerned with how the data is managed but instead just communicating within the API boundary and from whatever controller has called it.

Secondly, we have moved business rules from within the service to a more common model. Now if the model/object is needed elsewhere within the application it is now accessible as a standalone object independent of any service. Let's take a quick look at what this might look like

Original Service structure

We could write a service like the following (note this is just pseudo code but you should get the idea)

module MyApplication.Services {
    'use strict';
    export interface IVehicleService {
        searchForVehicle: (vehicle: any, identity: any) => any;       
    }

    export class VehicleService {

        public static $inject: any[] = [];

        public static factory(
            $http:  ng.IHttpService): IVehicleService {
        const routePrefix = '/someapiendpoint/vehicle';
            return {               
                searchForVehicle: (vehicle: any, identity: any) => {

                    //we need to build the request for submission
                    const request = {
                         id: vehicle.id,
                         userId: identity,
                         color: vehicle.color,
                         make:  vehicle.vehicleMake,
                         model: vehicle.vehicleModel,
                         engineSize: vehicle.engineSize,
                         licensePlate: vehicle.plate
                    }
                    return $http.post(routePrefix + '/name/' ,request).then((data) => {
                        //when the promise completes transform the result for the consumption
                        const result ={    
                            id : data.id,
                            color : data.color.id,
                            make : data.make.id,
                            model : data.model.id,
                            engineSize : data.engineSize,
                            licensePlate : 'Plate Number: '+ data.plate
                        } 
                        //return the result 
                        return result;
                    });
                }
           
            }
        }
    }

}
 
  

Pretty bloated right? Sure, we move out of the controller and into a service to handle the mapping and calls but now, we have a service that can only do one thing and is somewhat coupled to the view needs of the view.

Let's now look at creating some separation using a model

module MyApplication.Models{

        export interface ISportsCar {
        url: string;
        userId: number
        id: number;
        color: MyApplication.Constants.IColorOptions;
        make:  MyApplication.Constants.IVehicleMake;
        model:  MyApplication.Constants.IVehicleModel;
        engineSize: MyApplication.Constants.IEngineSize;
        licensePlate?: string;
    }
    
    export class SportsCar {
        url: string;
        userId: number
        id: number;
        color: MyApplication.Constants.IColorOptions;
        make:  MyApplication.Constants.IVehicleMake;
        model:  MyApplication.Constants.IVehicleModel;
        engineSize: MyApplication.Constants.IEngineSize;
        licensePlate?: string;
     
        //fn used to build the vehicle request for submission to the API
        buildSportsCarRequest= (obj:any, url:string, identity:number)=>{
            this.url = url;
            this.userId= identity;
            this.id = obj.id;
            this.color = obj.vehicleColor;
            this.make = obj.vehicleMake;
            this.model = obj.vehicleModel;
            this.engineSize = obj.engineSize;
            this.licensePlate = obj.plate;
            
            return this;
        }
        /*when a response is returned we can format the data for 
        how we want  it displayed in the UI*/
        formatSporstCarResponse = (car: any)=> {
            const result =  new SportsCar()
            result.id = car.id;
            result.color = car.color.id;
            result.make= car.make.id;
            result.model = car.model.id;
            result.engineSize = car.engineSize;
            result.licensePlate = 'Plate Number: '+ car.plate;
        }
    }    
}

Within the model we now define the object we want to work with, a sports car. Enumerated values are defined for the color, make, model, engine size etc. It's here where we could define business rules for how the model is handled. Things such as validation, formatting, and transformation of the data can be applied. If needed we can even inject additional models to extent the current base if we so desire. While somewhat anemic (this is just a sample) you can see how we now build within the model the request for submission to the API as well as the translation of data on the return. This model now allows us to update the service as follows:

module MyApplication.Services {
    'use strict';
    export interface IVehicleService {
        searchForVehicle: (vehicle: any,path:string, identity: any) => any;       
    }

    export class VehicleService {

        public static $inject: any[] = [];

        public static factory(
            $http:  ng.IHttpService): IVehicleService {
        const routePrefix = '/someapiendpoint/vehicle';
            return {               
                searchForVehicle: (vehicle: any,  path:string, identity: any) => {
                   
                   const vehicleModel = new MyApplication.Models.SportsCar();
                   
                   const request = vehicleModel.buildSportsCarRequest(vehicle, path, identity)
                    
                   return $http.post(routePrefix + request.url , request).then((data) => {                       
                      return  vehicleModel.formatSporstCarResponse(data);

                    });
                }
           
            }
        }
    }

}
 
  

We now have a more flexible service. Sure, we basically just moved the functions into a more common set of code/model but have now reduced the amount of code needed within the application. It now resides in one location and may be called or invoked by other areas of the site if they need to search for a sports car. Depending upon the need of the services it could be extended to conditionally build different requests if needed. The service is no longer responsible to manage or transform the data. This logic now resides within the model.

One final note when trying to separate concerns. Depending upon your frameworks try to make components, directives or views as anemic as possible. the less code and coupled you are to a DOM structure the better. Some would state that components or directives are exactly intended to be used for DOM manipulation and I do agree. Being able to develop a generic component that has reuse across views vs being tied to a specific view is much more valuable to the development team. One last example:

module MyApplication.Common.Directives {

    'use strict';

    export interface ISliderPillScope extends ng.IScope {
        sendMessage: () => void;
        message: string;
        content: any;
        data: any;
    }

    export class SliderPill {

        public static $inject: any[] = [];

        public static factory(): ng.IDirective {

            return {
                restrict: 'E',
                template: '<label class="slider"><input type="checkbox" 
                            ng-click="sendMessage()" ng-model="data">
                           <span class="slider round"></span></label>',
                scope: {
                    message: '=',
                    content: '=',
                    data: '='
                },
                link: (scope: ISliderPillScope, element, attrs) => {
                    scope.sendMessage = () => {

                        let term = scope.message;

                        if (angular.isDefined(scope.content) && angular.isDefined(scope.message)) {
                            scope.$emit(term, { message: scope.message, content: scope.content });

                        } else if (typeof attrs.message !== 'undefined' || scope.message !== null) {
                            scope.$emit(term);
                        }
                    }

                    scope.$on('$destroy', () => {
                    });
                }
            }
        }
    }
}
 
  

In this example I created a slider pill control (toggle) the directive accepts a message, content and data from the either controllers, or other directives it is placed. Click the toggle emits a message up the stack to trigger some other event. Notice there are no selectors calling specific elements. I'm allowing angular to manage the DOM via the framework and not creating a dependencies to any specific view or component. I now have a module component that can be used anywhere within the application.

Take away

Encapsulation within the presentation layer is a powerful discipline to learn and apply. Treat the presentation layer no differently than any other area of your application. Refactor services or interfaces to become as generic as possible to promote code reuse across your applications. Decouple views from business logic to allow for fast and efficient refactoring of DOM structures.



To view or add a comment, sign in

More articles by Randall Crews

  • Models and Modelers how they drive DDD

    A few fundamentals of Domain Driven Design (DDD) Domain driven design (DDD) is not always the best pattern to implement…

Others also viewed

Explore content categories