Compsys is a Typescript system architecture framework library. It is heavily inspired by Stuart Sierra's component library for Clojure.
A System is a javascript object constructed from a blueprint declaring and describing the named component roles, instances, and their dependencies. Components may be of any type.
Components may have dependencies. Dependencies are declared on roles and are
injected when the system is started. These components must have a property for
the module's inject
symbol whose value is a function accepting the role and
instance for each dependency. Component injection occurs at system start.
Components may have lifecycles. These are started when the system starts, in the
order required by the dependency graph, and stopped with the system stops, in
reverse order. Injected components with lifecycles are always injected after
being started. Components with lifecycles are replaced by their promised started
or stopped values, allowing for immutable components. Components with lifecycles
must have properties for the module's start
and stop
symbols, whose values
are async functions of no arguments which must return the component in its new
state.
There is an example blog server that includes explanatory and polemic commentary.
Most of the Javascript applications I've seen seem to suffer from a lack of architecture. The applications lack any encapsulation of and separation between phases of the application's existence, which leads to confused code paths, redundant code, ill-specified behaviors, bugs, etc.
Moreover, side effects tend occur all over the place, both internal and external, which complicates testing, reduces the possibility of code reuse, and impedes analysis.
Most applications can profitably be characterized by the following phases:
- Constructing configuration data from various sources, including the filesystem, envirionment variables, command-line arguments, inspecting the environment, calling services, etc.
- Validating the syntax of the configuration - not does the password work, but is it a non-empty string
- Constructing a system of components that have well-defined interfaces behind which all side effects occur
- Starting the system, at which point expensive resources are allocated, network connections are established, ports are bound for listening, etc.
- Stopping the system, affording components the explicit opportunity to finish work and clean up resources
This library intends to faciliate clarifying and correctly implementing the last three steps.
- Provide a principled approach to system architecture
- Facilitate encapsulating side effects within coherent substitutable components
- Separate and simplify the stages of an application's lifecycle
- Allow, but not mandate, immutable components
- Components play named roles, and depend on any number of other named roles
- Components may or may not have lifecycles
- Components may or may not have dependencies
- Use of the library should be able to write idiomatic Javascript using normal mutable objects and method signatures
- The library should have as few dependencies as possible
Expressing a component system can be a bit more code than building a system piecemeal. Writing interfaces and component implementations and builders for the various configurations of systems has a cost.
On the other hand, the construction of a component system tends to be code that's well-structured and easy to follow. Thinking about interfaces and components helps clarify the system's needs. Well-written coherent mock components can facilitate fast, broad test coverage at various levels of coupling, and can be induced to fail in ways that can be hard, slow, or simply unreliable to do with production components. Abstract components can swap production implementations to faciliate migrations.
MIT
Copyright Donald A. Ball Jr. 2019