Let DDD bring order to your JavaScript chaos
Source: https://dzone.com/articles/domain-driven-design-in-javascript
I wouldn't class myself as a
JavaScript developer, I always joke that it's a language I never meant to
learn. It's so pervasive now, it just happened. I go through phases of enjoying
it and despising it. But through the peaks and troughs of love and not quite
hate. One problem persisted: if I'm to be a good JS developer and write
functional JavaScript, how then do I write code in a way that implies a proper
domain model?
In traditional OO languages, such as
Java, C#, and even Go actually, it's easy to write code that's architected
around a domain design. You have classes, which are big and do a lot of stuff.
Which of course is something you generally avoid like the plague in JavaScript,
for fair enough reasons.
However, my code always seemed to
end up looking like this:
const
{ getUser, removeUser } = require('services/user');
const
{ sendEmail } = require('helpers/email');
const
{ pushNotification } = require('helpers/notifications');
const
{ removeFilesByUserId } = require('services/files');
const
removeUserHandler = await (userId) => {
const message = 'Your account has been
deleted';
try {
const user = await getUser(userId);
await removeUser(userId);
await sendEmail(userId, message);
await pushNotification(userId, message);
} catch (e) {
console.error(e);
sendLogs('removeUserHandler', e);
};
return true;
};
This looks okay, right? Sure! No big
problems here design-wise. However, when you have a large codebase entirely
made up of files such as this, in other words directories full of vaguely
grouped 'services,' individually exporting and importing single functions,
often vaguely named, and not obviously belonging to a domain when reading
through the code, it can very quickly feel as though you're dealing with a big
ball of unrelated scripts, rather than a well-architected software application.
I didn't want to return to classes
and traditional encapsulation. It felt like a step back after learning 'the
functional way™️. But, increasingly, I was finding JavaScript projects
difficult to read, 'bitty' and fragmented. I was seeing this everywhere, too!
It wasn't just my own hapless downfall. It seemed really common to see JS
projects with little to no design or architecture. I was ready to toss JS into
the bin for good and resume my position in the Golang ivory tower.
Until one of my engineers slipped a
new feature into one of our most noisy codebases, which jolted my attention.
Peering through reams and reams of
JavaScript, suddenly something stood out in a PR.
ScheduledJobs.run(jobId);
const
job = await ScheduledJobs.get(jobId);
Huh. Is that, a class? Surely not.
We don't do that here! No!
const
run = (jobId) => {};
const
stop = (jobId) => {};
const
pause = (jobId) => {};
const
get = (jobId) => {};
module.exports
= {
run,
stop,
pause,
get,
};
Praise Dijkstra, they're just
functions! Good old-fashioned functions. Suddenly I felt so, so very silly for
deliberating, Googling manically for weeks and weeks, and posting lengthy
diatribes on Twitter about how JavaScript was done; not fit for public consumption.
When all I needed to do was use what JavaScript gave me for this exact purpose:
modules! I got so caught up in trying to follow a paradigm that I forgot to be
pragmatic.
If I refactored my first arbitrary
example to use this pattern, in order to follow a domain design, maybe I'd have
something more like this:
const
UserModel = require('models/user');
const
EmailService = require('services/email');
const
NotificationService = require('services/notification');
const
FileModel = require('models/file');
const
Logger = require('services/logger');
const
removeUserHandler = await (userId) => {
const message = 'Your account has been
deleted';
try {
const user = await UserModel.getUser(userId);
await UserModel.removeUser(userId);
await EmailService.send(userId, message);
await NotificationService.push(userId, message);
return true;
} catch (e) {
console.error(e);
Logger.send('removeUserHandler', e);
};
return true;
};
This code tells me so much more
already!
I began writing my JavaScript in
this way, centered around these objects of grouped functions, which can still
be used in a functional way. But this pattern communicates purpose much better
than dealing in lots of single, un-grouped function calls. I find it made code
easier to follow, having that indicator of where this piece of code fits into
the bigger picture.
It was so simple in the end, and it
was something I already knew, even something I had already used hundreds of
times in the past. It all seemed so obvious! But it's easy to neglect concepts
such as DDD in languages like JavaScript, especially when you're on the pursuit
to functional enlightenment! But there is a happy medium.