Complex Controllers Challenge: PrOpER [coach]

posted in: coaching, Design, Standards | 0

Problem: Pick a problem to work on. Watch how the team works. What needs to be improved?
Options: Consider your options. What could you try that might influence the situation for the better? List at least three options.
Experiment: Pick one option to try.
Review: Review the outcome. Did you improve things? Even if things haven’t improved, have you learned something?

 

Problem:

The “dbis_drilling module – DbisDrillingController” is too complex. This is a problem we need to work on.

Source here.

Reviewed the code as a substitute for “Watch how the team works” and found a complex controller.

What needs to be improved?

  1. It lacks a design with components
  2. Separation of concerns like DOM manipulation, Business Rules, Business Model, UI widgets, API calls

 

Options:

What can I try that might influence the situation for the better?

  1. Review the code with the team and ask how they would improve the controller if they had the time
  2. Prepare a design workshop on “separation of concerns” and ask the team to demonstrate their understanding of the idea to the controller
  3. Prepare an example of my own which shows “separation of concerns” applied and study it with the team. Then ask them to show how that approach can be applied to the controller
  4. Try to separate out the controller into suggested design and prepare a presentation for the team to workshop further.
  5. Ask a AngularJS specialist to review the controller and make suggestions that either she or I present to the team.

 

Experiment:

Ask the team to help pick an option or combination of options or even to suggest new options that would work best for them.

 

Asking the team worked. They chose 3 and 4.

I am preparing 3 and 4 and will arrange time to meet with the team as soon as 3 and 4 are ready.

 

I prepared an example with Model View and Controller to show that a traditional approach to separation of concerns can be achieved in AngularJS which the team uses. I made a module in cleverstack format and wrote Unit and E2E tests for all the features. The source code is here http://cl.ly/Xuy0/download/pet.zip.

I added a second Model to the pet example to show how Models can collaborate to eventually achieve a Domain Driven Design like this http://know.iteonline.co.za/?p=1467 The source code is here http://cl.ly/XvCg/download/pet2.zip

To answer 3 more completely

I prepared a demo app which we can work on together to learn how to separate concerns.

The first version is like TodoMVC had it originally but I added a domain folder and a model to start the journey. http://cl.ly/XxdN/download/angularjs%20DDD%20MVC%20lite.zip

Then I re factored the whole structure into a modular arrangement like we see in Cleverstack. I also marked the functionality in the controller to be refactored by us together. This is where we will start. http://cl.ly/XxSm/download/angularjs%20DDD%20MVC%20Modular%20lite.zip

Revised structure:

http://cl.ly/Xxuv/download/angularjs%20DDD%20MVC%20Modular%20lite.zip
http://cl.ly/XxxR/download/angularjs%20DDD%20MVC%20Modular.zip

.
├── app
│   ├── TodoModule
│   │   ├── assets
│   │   │   ├── bg.png
│   │   │   └── todo.css
│   │   ├── module.js
│   │   ├── source
│   │   │   ├── todo-index.html
│   │   │   ├── todoCtrl.js
│   │   │   ├── todoEntity.js
│   │   │   ├── todoEscape.js
│   │   │   ├── todoFocus.js
│   │   │   └── todoStorage.js
│   │   └── tests
│   │   └── unit
│   │   ├── directivesSpec.js
│   │   └── todoCtrlSpec.js
│   └── app.js
├── bower.json
├── index.html
├── readme.md
└── test
├── config
│   └── karma.conf.js
├── package.json
└── readme.md

http://think-a-doo.net/AngularjsModularMVC/#/

 

In a second project which shows author names and lets you choose them to see quotes from each author we see the idea of refactoring to have a Model. When we are done separating concerns, the state of the application and the domain definition is all done in the Model.

Refactor from all Controller design to Model based Domain driven Design

Start: http://cl.ly/Xz8P/download/Demo.js

<!-- CACHE FILE: list.html -->
<script type="text/ng-template" id="list.html">
    <div class = "row options"> 
        <div class="large-2 columns options"> 
             <a ng-repeat="author in list" ng-click="selectQuote(author)" class="{{isSelected(author) && 'selected' || ''}}">{{author.name}}</a>
        </div>
        <div class="large-10 columns end padtop">
            <textarea name="quotetext" id="quotetext" readonly>{{selectedQuote}}</textarea> 
         </div>        
    </div >
</script>

angular.module('modelDemo', ['ngRoute'])
    .config(function ($routeProvider) {
        'use strict';

        $routeProvider.
            when('/author', {
                controller: 'AuthorListCtrl',
                templateUrl: 'list.html'
            }).
            otherwise({
                redirectTo: '/'
            });
    });

angular.module('modelDemo')
    .controller("AuthorListCtrl", ['$scope', function($scope) {
        'use strict';

        $scope.list;
        var fowler = {
            name: "Fowler"
        };
        var twain = {
            name: "Twain"
        };
        var poe = {
            name: "Poe"
        };
        var plato = {
            name: "Plato"
        };
        
        twain.quote = "Why, I have known clergymen, good men, kind-hearted, liberal, sincere" +
            ", and all that, who did not know the meaning of a 'flush.' It is enough " +
            "to make one ashamed of one's species.";
        fowler.quote = "Any fool can write code that a computer can understand. " +
            "Good programmers write code that humans can understand.";
        poe.quote = "Deep into that darkness peering, long I stood there, wondering, " +
            "fearing, doubting, dreaming dreams no mortal ever dared to dream before.";
        plato.quote = "All things will be produced in superior quantity and quality, and with greater ease, " +
            "when each man works at a single occupation, in accordance with his natural gifts, " +
            "and at the right moment, without meddling with anything else. ";
        
        $scope.list = [twain, fowler, poe, plato];   
        
        $scope.selectQuote = function(author) {
            $scope.selectedQuote = author.quote;
            $scope.selectedAuthor = author;
        }
        
        $scope.isSelected = function(author) {
            return author === $scope.selectedAuthor;
        }
    }]);

Re-design: http://cl.ly/XyNy/download/modelDemo.js

<!-- CACHE FILE: list.html -->
<script type="text/ng-template" id="list.html">
    <div class = "row options">
        <div class="large-2 columns options">
            <a ng-repeat="author in authorListModel.list" ng-click="selectQuote(author)" class="{{isSelected(author) && 'selected' || ''}}">{{author.name}}</a>
        </div>
        <div class="large-10 columns end padtop">
            <textarea name="quotetext" id="quotetext" ng-controller="QuoteTextCtrl" readonly>{{showSelectedQuote()}}</textarea>
        </div>
    </div >
</script>

angular.module('modelDemo', ['ngRoute'])
    .config(function ($routeProvider) {
        'use strict';

        $routeProvider.
            when('/author', {
                controller: 'AuthorListCtrl',
                templateUrl: 'list.html'
            }).
            otherwise({
                redirectTo: '/'
            });
    });

angular.module('modelDemo')
    .controller("AuthorListCtrl", ['$scope', 'authorListModel', function ($scope, authorListModel) {
        'use strict';

        $scope.authorListModel = authorListModel

        $scope.selectQuote = function (author) {
            authorListModel.setSelectedAuthor(author);
        };

        $scope.isSelected = function (author) {
            return author === authorListModel.selectedAuthor;
        };
    }]);

angular.module('modelDemo')
    .controller("QuoteTextCtrl", ['$scope', 'authorListModel', function ($scope, authorListModel) {
        'use strict';

        $scope.authorListModel = authorListModel.list;

        $scope.showSelectedQuote = function (){
            return authorListModel.selectedAuthor.quote;
        }

    }]);

angular.module('modelDemo').service("authorListModel", [function() {
angular.module('modelDemo')
    .service("authorListModel", [function () {
        'use strict';

        var fowler = {
                name: "Fowler",
                quote: "Any fool can write code that a computer can understand. Good programmers write code that humans can understand."
            },
            twain = {
                name: "Twain",
                quote: "Why, I have known clergymen, good men, kind-hearted, liberal, sincere, and all that, who did not know the meaning of a 'flush.' It is enough to make one ashamed of one's species."
            },
            poe = {
                name: "Poe",
                quote: "Deep into that darkness peering, long I stood there, wondering, fearing, doubting, dreaming dreams no mortal ever dared to dream before."
            },
            plato = {
                name: "Plato",
                quote: "All things will be produced in superior quantity and quality, and with greater ease, when each man works at a single occupation, in accordance with his natural gifts, and at the right moment, without meddling with anything else. "
            };

        this.list = [fowler, twain, poe, plato];

        this.selectedAuthor = {
            name: "Demo",
            quote: "Select an Author."
        };

        this.setSelectedAuthor = function (author) {
            if (this.list.indexOf(author) > -1) {
                this.selectedAuthor = author;
            }
        };

    }]);

 

Angularjs Modular MVC Todo Author http://cl.ly/XyTN/download/AngularjsModularMVCTodoAuthorLite.zip

This source combines the 2 demos in one application as modules.

The Todo app and the Author app are in the same project.

http://localhost:63342/AngularjsModularMVC/index.html#/todo

Screen Shot 2014-10-11 at 8.09.41 PM

http://localhost:63342/AngularjsModularMVC/index.html#/author

Screen Shot 2014-10-11 at 8.09.02 PM

 

.
├── app
│   ├── ModelDemo
│   │   ├── assets
│   │   │   └── modelDemo.css
│   │   ├── module.js
│   │   ├── source
│   │   │   ├── authorList.html
│   │   │   ├── authorListCtrl.js
│   │   │   ├── authorListModel.js
│   │   │   └── quoteTextCtrl.js
│   │   └── tests
│   │       └── unit
│   │           ├── AuthorListCtrlSpec.js
│   │           ├── AuthorListModelSpec.js
│   │           └── QuoteTextCtrlSpec.js
│   ├── TodoModule
│   │   ├── assets
│   │   │   ├── bg.png
│   │   │   └── todo.css
│   │   ├── module.js
│   │   ├── source
│   │   │   ├── todo-index.html
│   │   │   ├── todoCtrl.js
│   │   │   ├── todoEntity.js
│   │   │   ├── todoEscape.js
│   │   │   ├── todoFocus.js
│   │   │   └── todoStorage.js
│   │   └── tests
│   │       └── unit
│   │           ├── directivesSpec.js
│   │           └── todoCtrlSpec.js
│   └── app.js
├── bower.json
├── bower_components
│   ├── angular
│   │   ├── README.md
│   │   ├── angular-csp.css
│   │   ├── angular.js
│   │   ├── angular.min.js
│   │   ├── angular.min.js.gzip
│   │   ├── angular.min.js.map
│   │   └── bower.json
│   ├── angular-mocks
│   │   ├── README.md
│   │   ├── angular-mocks.js
│   │   └── bower.json
│   └── angular-route
│       ├── README.md
│       ├── angular-route.js
│       ├── angular-route.min.js
│       ├── angular-route.min.js.map
│       └── bower.json
├── index.html
├── readme.md
└── test
    ├── config
    │   └── karma.conf.js
    ├── package.json
    └── readme.md

 

Now that you have an idea of MVC separation and how it works, look at the next app which shows a more complex domain separated out into the Model. State and Rules of the chess game are both designed to work in the model. The controller handles interaction.

Analyse the separation of concerns in this app http://maaz.github.io/angular-chess/

 

To answer 4 I reviewed the Controller.

My suggestions are:

 

For UI Component separation look at https://leanpub.com/web-component-development-with-angularjs/read#leanpub-auto-chapter-6—ui-container-components-by-example

For Domain analysis:

Entities
========

Block
—–
Create
Read
Update
Delete

Hole
—-
Create
Read
Update
Delete

Collections
===========
ProductionHoles
ReliefHoles
ReDrillHoles

  1. Create self contained Components for complex controls
  2. Maybe Row could be a complex control

Consider:

Roll your own
Restangular
ngActiveResource
Breeze
Angular-Models

 

RESULT:

The team invited me to a design standards meeting (29 Oct 2014) where they asked: “What are our options?”

I presented a OOP and Functional example of moving the Domain Code out of the Controller and into Services.

After some consideration the team chose OOP pattern in the PET project http://cl.ly/YHNG/download/pet.zip

I am looking forward to see their contribution to this article.

 

BUT Not the RESULT we are looking for…

 

The code in the Controller in Question still has not changed to embody the OOP pattern and also has not been separated into Model/s, Controller/s, Views, Directives etc.

The team had a design meeting where all kinds of ideas were shared. Then nothing happened.

The ideal put forward long time ago is to follow DDD which keeps the Models clear from infrastructure and utility code.

The only examples we have do not offer a clean separation between AngularJS and domain Models.

 

It looks like the lack of a single authority with a design pattern proposal is needed.

 

I contacted AirPair to see if we can get such a definitive answer from some expert.

Leave a Reply