This section requires knowledge of the architecture. With growing experience these recommendations can change. Nothing is carved in stone here.
Files should be named lowercase. Different words in a file name should be split by a dot.
We learn from bad practice and therefore I want to explain why it makes sense to think twice about decoupling before writing some code.
In one of my workplaces we created a RequestHandler
class.
The purpose of this class was to handle requests for every endpoint.
Depending on every response, one or multiple toast messages could be dispatched.
I bet you already see the problem: This shit is totally coupled and therefore not reusable. I mean, what if we had another app without toasts? Or what if we wanted a caching mechanism for that one specific endpoint? Actually the weirdest thing in my eyes was following piece of code:
// once upon a time in the response callback function:
if (response.statusCode === 401) {
history.push('/login');
}
//...
What if we had no routes at all, for instance in an embeddable iframe app?
With this code, even ui
logic was mixed with domain
logic.
Routing to another url is definitely a ui
layer thing and has nothing todo with request handling itself.
Remember: A module can have dependencies to other modules.
For a better approach have a look at http-foundation
, http-api-v1
and http-api-v1-toaster
modules.
All these modules are decoupled but together they provide the same functionality as the
horrible coupled RequestHandler
class does.
To find out how these modules are plugged together,
just have a look at ./src/webapp/src/services.factory.ts
.
It is recommended importing things like
import { x } from 'foo';
and not by default exports import x from 'foo';
.
Codebase changes overtime, so could default exports.
Default exports are just another hurdle for changing code, because outside modules are coupled to it.
I recommend avoiding default exports at all.
It's recommended splitting your redux-saga logic into flows
and custom effects
.
This saga functions could also be called "handlers".
Flows
are hierarchically composed by service factories.
Usually the root factory is the service factory of an app.
Dependencies are passed down by these factories.
Side effects are happening in these sagas.
A effect
is an independent saga, listening for commands and events, without dependencies
By avoiding circular import references, imports stay clear and comprehensible. However, some bundlers have problems with circular import references. Below you find some helpful rules:
- Imports must only reach to the very next
index.ts(x)
file - Functionality of the same module should be imported relatively:
import { HttpFoundationEventTypes } from "./command";
- Functionality of another module should be imported absolutely:
import { executeRequest } from "packages/common/http-foundation
Imports always should reference to one specific layer. This avoids problems with bundlers.
Problem: Imagine there was a index.ts
in the root of the packages/common/translation
module,
which is exporting domain, native and web stuff.
If we would import things from this index.ts
file into our mobile app,
we had unwanted dependencies to web components and likely to additional web libraries.
Therefore it is important to import things from .../domain
, .../ui
or .../infrastructure
folders directly.
These folders should contain an index.ts
file to export the corresponding module's public api.
With linting rules all team members have the same understanding of code style. Linting helps to read code more quickly and therefore increases productivity. Within this project linting is done by eslint.
Jest is used as test runner. The domain logic (redux-saga) can be tested with redux-saga-test-plan. React components are tested by react-test-renderer.
I recommend meeting following rules for testing:
- The file suffix
.test.ts
is required - A unit test is placed next to the tested file. As an example the unit test for
foo/bar/baz.ts
is/foo/bar/baz.unit.test.ts
. - Tests should either have the suffix
.unit.test.ts(x)
or.integration.test.ts(x)
.
Following information is not necessary to know but may be interesting for you.
I think, because snapshot tests are expected to fail with every UI change, they are completely useless for TDD. In my opinion, tests are here to develop faster and especially to prevent unwanted bugs. Similar thoughts here.
Read a smart article about testing structure.