Design Values

 

General Design Values

Useful Objects

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.

Controllable

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".

Behavioral Objects

Objects are behaviors, not data. They expose a consistent, executable interface rather than a set of behaviors.

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 themselves.

Explicit Dependencies

All dependencies are explicit, and reified and declared explicitly.

Substitutes

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. 

Telemetry

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.

Logging

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.

Composition

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.

Class Constructors

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.

Protocol Discovery

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.

Controls

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.

Autonomy

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.

No Queries

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.

Components

Services host components at runtime. Components provide a standard, discoverable interface to the service runtime.