Express

and

Clean Architecture

@EggDice

lab.coop

Lets implement a feature in express

  • Join a group by clicking a button
  • Flash a message: "Joined X group"
  • If the 1000th join redirect to the /celebrate page
  • otherwise redirect back

Usual way

MVC

~/test/mvc
$ ls -la
total 20
drwxr-xr-x  5 egg egg 4096 Feb 19 16:35 .
drwxr-xr-x 12 egg egg 4096 Feb 19 16:34 ..
drwxr-xr-x  7 egg egg 4096 Feb 19 16:34 .git
drwxr-xr-x  2 egg egg 4096 Feb 19 16:34 controllers
drwxr-xr-x  2 egg egg 4096 Feb 19 16:34 models
drwxr-xr-x  2 egg egg 4096 Feb 19 16:35 views
drwxr-xr-x  2 egg egg 4096 Feb 19 16:35 lib
-rw-r--r--  1 egg egg    0 Feb 19 16:35 app.js
-rw-r--r--  1 egg egg    0 Feb 19 16:35 .eslintrc
-rw-r--r--  1 egg egg    0 Feb 19 16:35 .gitignore
-rw-r--r--  1 egg egg    0 Feb 19 16:35 package.json

We need a Controller

var groupModel = require('../models/group');

app.get('/group/:id/join', function(req, res, next) {
    groupModel.joinGroup({
        id: req.params.id,
        userId: req.user.id
    }).then(function() {
        return groupModel.getGroupById({
            id: req.params.id
        });
    }).then(function(group) {
        res.flash('success', res.__('joined', { groupName: group.name }));
        
        if (group.count === 1000) {
            res.redirect('/celebrate');
        } else {
            res.redirect('back');
        }
    }).catch(next);
});

We need a Model

var db = require('../lib/db');

function getGroupById(params) {
    return db.select('groups', {
        id: params.id
    });
}

function joinGroup(params) {
    return db.insert('user-group-join', {
        groupId: params.id,
        user: params.userId
    });
}

module.exports = {
    getGroupById: getGroupById,
    joinGroup: joinGroup
};

Lets test the Controller

var groupModel = require('../models/group');

app.get('/group/:id/join', function(req, res, next) {
    groupModel.joinGroup({
        id: req.params.id,
        userId: req.user.id
    }).then(function() {
        return groupModel.getGroupById({
            id: req.params.id
        });
    }).then(function(group) {
        res.flash('success', res.__('joined', { groupName: group.name }));
        
        if (group.count === 1000) {
            res.redirect('/celebrate');
        } else {
            res.redirect('back');
        }
    }).catch(next);
});

req

req

res

res

The only way to test this: Mock the HTTP request

Problems

Testing

Separation

Hapi, restify whatever

There is another way

Uncle Bob

Robert C. Martin 

https://www.youtube.com/watch?v=Nsjsiz2A9mg

~/test/mvc
$ ls -la
total 20
drwxr-xr-x  5 egg egg 4096 Feb 19 16:35 .
drwxr-xr-x 12 egg egg 4096 Feb 19 16:34 ..
drwxr-xr-x  7 egg egg 4096 Feb 19 16:34 .git
drwxr-xr-x  2 egg egg 4096 Feb 19 16:34 group
drwxr-xr-x  2 egg egg 4096 Feb 19 16:34 profile
drwxr-xr-x  2 egg egg 4096 Feb 19 16:35 message
drwxr-xr-x  2 egg egg 4096 Feb 19 16:35 lib
-rw-r--r--  1 egg egg    0 Feb 19 16:35 app.js
-rw-r--r--  1 egg egg    0 Feb 19 16:35 .eslintrc
-rw-r--r--  1 egg egg    0 Feb 19 16:35 .gitignore
-rw-r--r--  1 egg egg    0 Feb 19 16:35 package.json

How should we implement a feature?

Interactor

Delivery

Entities

Boundary

A Feature

Interactor

Delivery

Entities

Boundary

Request

On user request

Interactor

Delivery

Entities

Boundary

Response

Update the UI

Interactor

Delivery

Entities

Boundary

Delivery

Our Controller (Delivery)

var joinGroup = require('../group/join');

URLS = {
    celebration: '/celebration',
    back: 'back'
}

app.get('/group/:id/join', function(req, res, next) {
    var requestModel = {
        userId: req.user.id,
        groupId: req.params.id
    }

    joinGroup(requestModel).
        then(flashAndRedirect).
        catch(next);
});

function flashAndRedirect(responseModel) {
    res.flash('success', res.__('joined', responseModel));
    res.redirect(URLS[responseModel.redirectTo]);
}

Interactor

Delivery

Entities

Boundary

Interactor

The Interactor

var groupEntites = require('/groupEntities');

function joinGroup(requestModel) {
    return groupEntities.
        get(requestModel.groupId).
        joinUser(requestModel.userId).
        then(resolveJoin);
}

function resolveJoin(group) {
    var responseModel = {
        groupName: group.name,
        redirectTo: group.count === 1000 ? 'celebration' : 'back' 
    };
    return responseModel;
}

module.exports = joinGroup;

So our PO is happy,

 because our software

not just works,

but it's easy to modify! 

Q & A

Made with Slides.com