General Design Values
A good deal of the Eventide design values are expressed in the Doctrine of Useful Objects. Please refer to the writeup for greater detail and examples.
All units can be instantiated and exercised directly without the need for the presence of any special infrastructure.
Push, Rather than Pull
Prefer giving instructions to design units, rather than pulling data out of them and working on it outside of its natural environment. Respect encapsulation. Respect "Tell, Don't Ask".
Objects are behaviors, not data. They expose a consistent, executable interface rather than a set of data attributes. While objects have to be used to model data in an object-oriented language, such data objects should be the minority of objects in your system. ORM tends to invert and contradict this quality of objects, leading to the kinds of intractable coupling that ORM-based codebases tend to suffer from.
Limited Data Object Interfaces
As opposed to ORM, a small minority of objects should be data structures. Data objects have an interface that operates only on their attributes. They do not serialize themselves, transform themselves, save themselves, validate themselves, or otherwise serve a purpose other than the representation and housing of data attributes. Behaviors on data objects pertain directly to the attributes on a data object, and do not offer any capabilities other than the representation of data as an object.
All object dependencies are explicit, and reified and declared explicitly.
At a minimum, dependencies are initialized to inert substitutes of their interfaces (null objects). Specialized substitutes are provided when needed that enable greater control of the dependencies. They are never nil, and never result in null reference errors.
Principal and integral objects, like I/O, provide telemetry. Telemetry sinks must be activated explicitly in systems in operational postures. Diagnostic substitutes may be configured for telemetry by default.
Principal and integral objects log their behaviors. Logging is written to standard error. Redirecting and recording of logs is done at the operating system level. Affordances for controlling the logging from outside of the running process are provided.
Designs are composed of independent and standalone units that can be actuated independently, used entirely on their own, and can be recomposed in ways not predicted by their authors.
Limited Primitive Initialization
Initialization of an object is limited only to the logic needed to pass state to a new object. Only primitive values are passed to initializers and the initializer can only record those primitive values as instance variables. Initializers don't deconstruct values passed to them.
Classes provide a constructor that are more convenient to use than limited, primitive initializers. Class constructors manipulate the values passed to them, and delegate to the initializer.
Objects never validate, serialize, or otherwise transform themselves. However, they can provide a means of discovering how to do so that doesn't entangle such concerns with the objects themselves.
Strict Regulation of Monkey-Patching
The runtime modification of highly-afferent classes (Object, Kernel, etc) and primitives (String, Number, Time, etc) is done with the strictest discipline. Additionally, any monkey patching must be explicitly activated rather than implicitly activated by loading a file. Any macros added to foundational classes like Object must optionally provide controls to extend them instead into specific classes in order to limit the scope of runtime modifications of existing afferent implementations.
Libraries, whether utility or applicative, provide canonical examples of the objects contained within. These controls serve as references and affordances to help users understand the intension of the library's implementations, for exercising implementations that use the library, for demo data, or diagnostic uses.
Service Design Values
Commands and Events
Services receive commands, and in response issue events representing the status of the handling of the commands. They may also handle events, allowing them to be aware of and react to the actions of other services.
A service that has become unavailable doesn't cause other services to become unavailable. Services that query other services in order to get their work done will become unavailable in a cascade of timeouts or failures.
Services don't query the data from other services in order to do their work. A service operates on its own data resources, and doesn't serve that data to other services or requests. Services don't provide query interfaces. Data retrieval and storage is the sole domain of databases. Data aggregators may process the events from an array of services in order to compose view data that can be queried by applications, user interfaces, and analytics.
Dump Pipes, Smart Endpoints
Microservices are characterized by the avoidance of "smart pipes" and other forms of smart messaging transports and infrastructure. More elemental transports are preferred to the smarts of message brokers, orchestration frameworks, with on-premise or hosted services.
Architecture, Not Infrastructure
Although often mentioned in the same breath, services have nothing to do with Docker, RPC, HTTP, cloud providers, GraphQL, or any particular infrastructure technology. Services are an architectural style that are separate from infrastructure.
Projection Side Effects
Projections apply events to entities. They don't cause any side effects other than invoking behaviors on and assigning data to entities.
Entities are Data Structures
Entities are simple data structures. If an entity has behavior, the behavior should pertain only to the entity's attributes.
Immutability of Messages
Events and commands are immutable once they are written.
Services host components at runtime. Components provide a standard, discoverable interface to the service runtime.