User:DKinzler (WMF)/Software Design Practices

Lemmas


 * Two components that have a (public) circular dependency (directly or indirectly) do not behave like separate components, but like a single component: when changing one, we can not be sure that the other does not need changing too.
 * Modularization is the process of decoupling by removing circular dependencies between components.
 * The two preferred types of objects are immutable values and "stateless" services. Auxiliary types of objects are mutable value objects, builders, and cursors (which include iterators and streams). Some commons kinds of services include factories/registries and persistence services.
 * Operations offered by services should be idempotent (with the exception of persistence services).
 * Only value objects should be "newable", services should never be instantiated directly by application logic. Cursors should be obtained from factory services.
 * The code for instantiating services constitutes the "wiring" of the application, and should be isolated as much as possible from application logic.
 * Dependency injection: Services shall ask for all required configuration and all required services in their constructor. Optional services and configuration that has defaults may use setters. Services should not rely on global state, any reference to a global variable or call to a non-pure static function is technical debt.
 * Interfaces make bad extension points, because they cannot be changed without breaking implementations; use abstract base classes instead.
 * Only pure functions shall be static methods (or global or namespaced functions).
 * Subclassing should never be used just to share code. Use composition and traits for code sharing instead. Static composition (one service-like object directly instantiating another service-like object in its constructor) is acceptable, but injection is generally preferred.
 * Service containers should never be injected into service objects. Generic configuration objects should never be injected into service objects.
 * Lazy initialization in value objects is acceptable and sometimes necessary, but should not be taken lightly. There should always be a non-lazy alternative (e.g. an alternative implementation).
 * There shall be no circular dependencies between modules, public or private.
 * Circular runtime dependencies between modules are ok, but should be handled with care, and should be documented when expected/desired.
 * There shall be no circular public dependencies between any components (modules, namespaces, classes, functions, etc).
 * Circular private dependencies may exists between members (sub-components) of the same higher level component (e.g. classes in the same namespace may depend on each other internally, methods of the same class may call each other internally, etc).
 * In the namespace hierarchy, it's also acceptable for classes in (transitive) parent namespaces and (transitive) child namespaces to depend on each other internally (but not publically). There shall be no circular dependencies (public or private) between sibling or more remotely related namespaces (nor between unrelated namespaces, of course).
 * All classes are considered part of their defining module's public interface, unless they are marked as internal. Internal classes are still part of the public interface of their namespace. They however must not be referenced from outside the module.
 * TBD: Amorphous parameters and extensibility

Definitions


 * Compontent: a collection of code with a well defined public interface. A typical example of a component are classes, but namespaces, modules, and even individual functions can be considered components.
 * Module (aka package, aka library, aka bundle): a set of components that share a versioning history and release cycle. In practice, this usually means things that are in a single git repo. In some cases, a single repo may contain multiple modules (or proto-modules) with the intention to move them into separate repos in the future.
 * Public dependency: any mention of (or knowledge of) another component in the public interface of a component. Public dependencies constitute tight coupling. Note that protected methods shall be considered part of the public interface of a class. The constructor signature of a class is considered part of the public interface if the class is considered "newable".
 * Private dependency (internal dependency, implementation dependency): mention of (or knowledge of) another component merely in the private code or declarations of a component.
 * Runtime dependency: once component using another component at runtime.
 * Newable classes: classes that application logic may instantiate directly, as opposed to requesting an instance from a factory or from a service container.
 * TBD: Static entry point:
 * TBD: Service container:
 * TBD: Service locator:
 * TBD: Internal class: