Modules
This is not specific to TypeScript, but still I would
like to talk about it. The way you organize your code
and impose boundaries between different parts of your
project is very important to how effectively you can
make use of it. TypeScript is going to be an important
tool to shape those boundaries creating contracts
between different parts of your code.
Splitting your code by domains
A domain represents a specific area of functionality
within an application, such as user management, payment
processing, or product inventory. By organizing your code
around domains, you can reason about it better.
Navigating around a project organized like that is easier
because its folder structure is expressive and you know
where to put your code or where to find what you need to
modify.
Having a
core
or features
or modules
folder (or any
other name you wanna give it) with a folder for each domain,
makes it much easier to know where the login or payment
feature is implemented.If you have experience as a professional software developer
you most likely have seen a project that had these three
folders at the top level: ‘models’, ‘views’ and ‘controllers’.
This was very popular for a while, but in fact, it’s not a
great way to organize a project, often causing it to rotten
quite quickly.
MVC is a great design pattern, but using it to organize all
of your code as a top-level architecture is misusing it.
It fails to make your folder structure expressive of what
its domains are and it spreads domains along those three
different folders instead. MVC was created for dealing with
GUI and not for organizing a whole application. That is why
working on a big MVC application feels like hammering screws
on a plank.
Creating boundaries to external code
One time I went to check on one of those bugs that happen
every now and then, but no clear reproduction scenario was available.
I debugged it for a bit and checked which libraries it was
using, finding a real sus one. Unmaintained for 4 years and
full of open issues open on GitHub. I did a quick search on
the issue and more people were relating the same thing.
It was clear to me that I needed to refactor the project to
remove that library. I was lucky it was a small project with
not a lot of lines of code, but I was unlucky in that it was
a library that was widely used in that project. It affected
every feature and had crept its way into almost every file.
Most libraries should not be used directly, as it will make
your project depends on them. Sometimes you might need to
swap them for a different one. Either because they have become
insufficient technically or because of a business decision in
the case of paid external services and libraries.
It is much better to invert the dependency when possible and
define how your code expects to, for example, send a push
notification to a customer independently from what library is
being used to do the chore.
A very basic implementation of that would be to simply define
an interface such as:
interface PushManager {
send(customerID: string, message: string): void;
}
And then create classes that map it to the actual implementation
for a specific service.
// serviceA.ts
import { send } from 'serviceA';
export class PushServiceA implements PushManager {
send(customerID: string, message: string) {
send(customerID, message);
}
}
// serviceB.ts
import ServiceB from 'serviceB';
export class PushServiceB implements PushManager {
service = new ServiceB();
send(customerID: string, message: string) {
this.service.sendPush({
identifier: customerID,
body: message
});
}
}
You can see that
serviceA
and serviceB
have different
API and way of working to accomplish the same goal. But with
this implementation you can swap between them quite easily
since your code only needs to know the signature of the
PushManager
interface and doesn’t need to care about how
it is implemented.I would recommend segregating the code that deals
with libraries and external services from your core code
where your business logic is implemented. Not all frameworks
and libraries reward this practice, so don’t go crazy
about it as well. Perfect is the enemy of good.