Microservices, a functional approach

Microservices

The microservice architectural style is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API

-Martin fowler

How does functional help?

  • A set of operations, performed on an incoming payload

  • Executed asynchronously
  • Defined responses, indicating success/failure of the operations, allowing application handling on either case

Describing a microservice, in a functional pattern

Example Application

  • Finds all talks, which contain a given search term
  • Finds speaker data for each talk
  • Joins them into a result set 

Search LNUG talks, and get speaker information.

findTalks :: TalkQuery -> Future Error [Talk]
addSpeakers :: [Talk] -> Future Error [TalkEmbeddedWithSpeaker]

High level overview : via type signatures

search :: TalkQuery -> Future Error [TalkEmbeddedWithSpeaker]
// Talk
{ 
    "title": "Keep calm and curry on",
    "description": "In this first talk we will look....",
    "speaker": 'cullophid'
}

// Speaker
{
    "handle": "cullophid",
    "name": "andreas",
    "surname": "moller",
    "title": "developer"
}

// TalkEmbeddedWithSpeaker
{ 
    "title": "Keep calm and curry on",
    "description": "In this first talk we will look....",
    "speaker": {
        "handle": "cullophid",
        "name": "andreas",
        "surname": "moller",
        "title": "developer"
    }
}
findTalks :: TalkQuery -> Future Error [Talk]
searchElasticSearch :: TalkQuery -> Future Error {hits:{hits:[{_source:Talk}]}}
parseResults :: {hits:{hits:[{_source:Talk}]}} -> [Talk]

Our first Asynchronous function

composed with post transformation

searchMongo :: SpeakerQuery -> Future Error [Speaker]
mongoQueryFromTalks :: [Talk] -> SpeakerQuery
findSpeakersFromTalks :: [Talk] -> Future Error [Speaker]

Second Async operation

composed with pre transformation

addSpeakers :: [Talk] -> Future Error [TalkEmbeddedWithSpeaker]

Result join function

innerJoin :: String -> String -> [Talk] -> [Speaker] -> [TalkEmbeddedWithSpeaker]
findSpeakersFromTalks :: [Talk] -> Future Error [Speaker]

DEMO TIME !!

TALKFINDER2000.UK

var search = require('./search');

app.get('/', function(req, res) {
    search(req.query)
        .fork(
            err => res.status(err.status || 500).send(err.message),
            r => res.json(r)
        )
});

Push the side effect to the edge

const findSpeakersFromTalks = talks => {

	var handles = pluck('speaker');
	var query = { handle: { $in: handles } };

	return mongoClient.collection('speakers').find(query);
}

Testing: functional vs imperative

Forced to mock, or rely on Dependancy injection

var findSpeakersFromTalksUsingClient = curry((searchFn,handles) => {
	var query = { handle: { $in: handles } };
	return searchFn(query);
})

Testing : pure functions

var findSpeakersFromTalks = 
     findSpeakersFromTalksUsingClient(mongoClient.collection('speakers').find);
const search = require('../handlers/search');

describe('GET /', function() {
    it('respond with json', function() {
        
        //only testing the transformation, and not the client
        var obj = search.speakerQueryFromTalks([{
            speaker: "TestName"
        }]);

        expect(obj).to.have.property('handle');
        expect(obj.handle).to.have.property('$in');
    });
});

Only test the transformations

Microservices, a functional approach

By James Chow

Microservices, a functional approach

  • 170