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
- 173