Name Construct and Structure
Name Construct and Structure
by Khalil Stemmler
une 16th, 2019.
This is version 1.0. It was published J
Newer versions can be downloaded from h
ttps://khalilstemmler.com/resources.
Found typos? Want to suggest an update? R
each out to me, I’m pretty receptive to
feedback.
Check back @ khalilstemmler.com for revisions.
© 2019 - Khalil Stemmler
Table of Contents
Nice to meet you! 4
Introduction 5
Organizing code more effectively 6
What does it mean when code is readable? 6
About this book 6
Name 7
Tips for picking good names 8
Approach #1: Name files by Domain + Construct 8
Approach #2: Name folders by Subdomain, and (optionally) Subdomain +
Construct 12
There’s a construct for everything 13
Learn into Design Patterns 14
Bad names 15
Look out for too many helper/utility/service classes 15
Construct 16
Each construct is responsible for one type of behavior 16
A construct instance should never be responsible for more than one subdomain
element 17
Architectural Boundaries 20
Structure 22
Project size 23
Package by Infrastructure 24
At a glance, this tells us nothing about the project 25
Features are developed vertically, not horizontally 26
Package by component 27
Package by component for everything 28
Univjobs’ React-Redux App Structure 28
Tips for good structure 29
Nesting rules 29
Shared things 29
In Summary 30
Names 30
What to look out for 30
Constructs 30
Structure 30
A real life project 30
Nice to meet you!
What’s up? I’m Khalil Stemmler from k
halilstemmler.com. Thanks so much for
downloading this free ebook. I sincerely hope it’s useful to you.
My goal is to help developers learn best practices towards creating and maintaining
large scale typescript & node.js applications. I frequently publish blog posts, guides,
books, and courses for developers building enterprise software with JavaScript &
TypeScript.
If you’d like to leave feedback on this book, suggest any other topics for me to cover,
or simply just chat, check me on Twitter @
stemmlerjs and see what I’m working on
@ GitHub @
stemmlerjs.
Introduction
Have you ever come back to an old codebase and felt like it was way too hard to
understand w
hat things are, what they do, and w
here they’re located?
I came about realizing that I wanted to write a short ebook on organizing codebases
for readability when I was tasked with adding new code to an o
ld project I created for
Univjobs a few years ago.
It took me w
ay longer than I wanted it to have taken me to get things done. I found
that I was spending a lot of time trying to figure out where things were and what they
were responsible for.
Over the past year of writing code using D
omain-Driven Design, I’ve paid a lot more
attention to how I organize my code. Also over this time, I’ve experimented with a
bunch of different approaches to code organization and noticed a few patterns for
extremely readable code.
To summarize, it comes down to how we organize our files and folder with respect
to the n
ames, constructs & structure.
Naming files well, using well-understood technical constructs, and structuring our
projects well are three ways to improve our ability to c
hange code quickly.
Having a
ll three of these when we’re organizing codebases makes them highly
readable.
“There are only two hard things in Computer Science: cache
invalidation and naming things”. -- Phil Karlton
Secondly, we should understand what c
onstructs a
re.
Since we understand what s
ubdomains a
nd c
onstructs are now, we could use them
to assemble names for our files by appending the c
onstruct type to the s
ubdomain
(or element from the subdomain - see the image on the next page).
Let’s see what some of that might look like in principle.
It’s a good start, but there’s a problem. W
e’re not organizing our subdomains
properly.
Things from the J
ob subdomain are mixed up with the U
sers subdomain. That’s
definitely giving me clutter-y feels.
It’s time to think about naming folders.
Approach #2: Name folders by Subdomain, and (optionally) Subdomain
+ Construct
Naming folders by the name of the s
ubdomain is a great way to segment code so
that you can focus on one particular part of your project at a time.
Here’s a start.
That’s better, but you might still feel a little bit discombobulated looking at this many
files at one time. I like to take the next step of also creating sub-folders and naming
them by the c
onstruct type.
I like this a lot. We talk about it more in P
art 3: Structure, but this is called
“Packaging by Component”.
Construct
What it does
Constructs are the tools (or patterns) in our software development toolbox that we
plug together to build web applications (mappers, controllers, views, models, etc).
This J
obMapper is pretty narrowly scoped. It’s only responsible to mapping the J ob
domain entity. That’s great.
Now, let’s say that we needed to also return the U ser that the Job belonged to as a
key called o
wnedByUser in the JobDTO like this:
Where the U
serDTO has the following properties:
What should we do with our t oDTO() method on the J obMapper?
Should we also define how we map U sers to U
serDTOs from within the J
obMapper’s
toDTO() method like this?
No. At this point, we should create another Mapper: U
serMapper.
And then delegate that responsibility to the U
serMapper in our J
obDTO.
Much better.
What we’re doing here is performing quality control on our architectural boundaries.
Architectural Boundaries
If we have a construct named "UserMap" in an application that trades vinyl (we
actually will soon , check out this repo), we should be careful thinking about our
architectural boundaries when we mention entities from other subdomains in our
application, like "vinyl" or "billing", from the “users” subdomain.
Here’s an example of a U
serRepo interface from my D
omain-Driven Design with
TypeScript course.
Yes, this is a UserRepo in the users subdomain, but it seems like the observable
responsibility is actually to r etrieve V
inyl.
It would probably make more sense to create a V
inylRepo.
These are the types of constant discussions that we need to have if we care about
software design and architecture. We’ll n
ever get everything right the first time
around.
If we keep our code siloed in architectural boundaries, it l eaves options open for us
in the future to potentially do many different things if our project grows. Some of
those things are to:
a) delegate ownership of parts of the project to separate teams
i) If we keep boundaries at bay, this means we’ll be able to break apart
the application so that separate development teams could set up their
own version control and testing pipelines.
b) microservice deployment
i) If we keep boundaries at bay, we can not only separate our code
logically, but physically too.
ii) Code can be split into separate repos and communication between the
deployments could take place over the network (HTTP, RabbitMQ, etc).
Either way, as codebases grow larger, and more developers join our company, we
look for ways to improve productivity and at scale. Keeping an eye on boundaries
can make transitions a whole lot less painful.
Structure
Where it belongs
Structure is most concerned with w
here we place files and h
ow we lay out our
folders. Structure is code organization and asks the question, “are things where
they’re meant to be”?
When our first guesses towards finding a particular file are correct, it greatly
improves productivity.
When we have to guess and flip through too many files and folders, our productivity
tanks.
Project size
This is increasingly difficult as projects grow in size. When projects are small, it
might be pragmatic to use a flat file structure and simple, yet generic names.
Take this file structure example of a script written to automate users into a
MailChimp List.
How much can you tell about the project from this structure and the file names?
An assumption would be that the m
ailchimp.js does the bulk of the work. Maybe it’s
what actually connects to the MailChimp API. Not sure what l ist.js is responsible for,
but since we have so few files, I wouldn’t quarrel too much with this.
As soon as the project starts getting more complex, you can be sure I’ll be looking
for ways to be more declarative with these files and folder names.
Package by Infrastructure
This is an important topic.
Most developers starting out, package their applications by infrastructure. This is the
opposite of packaging your application by component.
Package by infrastructure is when we name our t op level folders by the construct
name.
To illustrate, here’s the folder structure of the old project that I was working on that
spawned my motivation to write this book.
If it’s not obvious why this is n
ot useful, let me make an analogy.
Imagine picking up a dictionary looking to find a particular word.
When you open the dictionary, it’s not sorted by a
lphabetical order, but instead it’s
sorted by “the types of words”.
So “long words”, “short words”, “scary words”, “funny words”...
It would take you much longer and considerably more cognitive energy to find what
you’re looking for.
That’s what we’re doing here with p
ackaging by infrastructure.
Features are developed vertically, not horizontally
When we develop features, it pretty much cuts across the entire stack. For example,
let’s say a POST request gets fired off by the client to C
reate a User. Here’s how that
might cut across several constructs.
Imagine that all of those constructs are in separate folders, away from each other.
That means, in order to develop one feature, you have to f lip between several layers
of folders to find the right construct to edit.
I believe we as humans can only remember at max 7 or so things at one time. So this
approach isn’t very human-friendly.
So let’s go back to the human-friendly approach.
Package by component
In order words, packaging to be b rain-friendly.
Here’s that same project, but this time refactored to use package by component.
Doesn’t that tell you so much more?
Package by component for everything
I used to think that this only really applied to backend code, but now also convinced
this is the way to organize code i n general.
Everything related to a particular feature (container components, validation logic,
styles, services, redux, models, etc) lives in a s
ingle individual feature module.
This has made it incredibly easy to identify where I need to go in the project to
change code.
So here’s my approach for good structure:
Tips for good structure
1. Start the first layer by identifying the features (or components or modules,
whatever you want to call it).
2. The second layer in holds the constructs. Refer to the construct names here.
Nesting rules
Sometimes, we’re in a feature that is split into several sub-features.
I’ll repeat #1 until I’m at the lowest sub-feature and then implement #2.
Shared things
Be sure to identify when things are shared and place them in a shared module. For a
good example of this, see the s
ource code for khalilstemmler.com.
In Summary
Name, Construct and Structure all contribute to the readability of your project. As
projects grow, it becomes harder to keep the same level of productivity up, so
special attention to these three things becomes vital.
Names
What it is.
Constructs
What it does.
Structure
Where it belongs.