- How many people code JS? Informs how much time I should spend showing exact patterns, instead of telling you they're possible.
- My experience: 170K LOC eReader application for BN.com.
- A lot of the concerns are the same as in any language. So, much of it comes down to what twisted tricks are necessary.
- Set the stage. Modern JS:
- Uses modules
- Uses packages
- "use strict";
- Encapsulation and coupling.
- Encapsulation goes against the JavaScript grain, generally.
- You can do private variables, but e.g. protected is impossible, and it has performance implications.
- No notion of "internal" modules.
- Much ends up being enforced by convention: people don't touch properties with underscores, and don't reach into your package.
- Classes do not exist. Classical inheritance does not apply.
- An object can be a prototype for another object. "Fallback" concept. Methods go here; data stays on instances. Then the methods get called with a
this
pointing to the instance. - But classes are still damn useful as a domain-modeling concept:
- Essentially, it becomes an issue of using factories, to produce objects with the same shape every time. And there's some (confusing) sugar for that, involving the
new
keyword. - You can recreate the notion of classical inheritance by first creating an object of one canonical shape ("class"), then create a second object, then use the first as the prototype of the second. If this process is encapsulated in a factory you get a "derived class" factory.
- Essentially, it becomes an issue of using factories, to produce objects with the same shape every time. And there's some (confusing) sugar for that, involving the
- Bad news: this prototypal way of doing classes, is entirely incompatible with encapsulation.
- There is an alternative, the closure pattern, which may very well be a good fit for your problem space. That is what we used.
- It involves reattaching methods to every object instance, so that they can access an instance's private state, instead of putting them on the prototype shared among all instances.
Services, repositories much the same.
Common practice is to make them singletons, since you can mock their dependencies without much work. But, this is not necessarily great convention.
= packages. Often correlated with git repos.
- There is no standard
.equals
method; noIEquatable<T>
interface; no operator overloading :(. - In practice, this is not a huge issue:
- I rarely need to compare value object equality, except in tests.
- I compare entities by their IDs, e.g.
product.id === product.id
, or use repositories to tightly control their creation so they are reference-identical as well. - It's really more about the semantics, anyway.
Object.freeze
- Encapsulation issues rear their head here.
- Factories are highly important to maintain it, if you're going to try.
More later
- Side-effect-free Functions
- Intention-revealing interfaces
- Assertions
- Standalone classes
- Conceptual contours
- Domain events
- Event sourcing
- Document databases, JSON, no ORMs
- Culture of BDD and integration testing
- Bounded contexts: keep them! Don't use JavaScript's flexible nature to blend the lines. Enforce the boundaries!