Clean Cloud Architecture & Terminology โ
hyper is built using a "Ports and Adapters" approach.
The general idea with Ports and Adapters is that the business logic layer defines the models and rules on how they interact with each other, and also a set of consumer-agnostic entry and exit points to and from the business layer. These entry and exit points are called "Ports". All components external to the business layer, interact by way and are interacted with, the Ports. A Port defines an api but knows nothing about the mechanism or the impetus. Those two things are an Adapter's job.
Adapters perform the actual communication between external actors and the business layer. There are generally two types of Adapters: "Driving Adapters" and "Driven Adapters".
A driving adapter calls into the business layer, by way of a Port. The driving adapters can generally be thought of as the "presentation layer". It could be a web application, a desktop application, a CLI, anything that initiates some action on the business domain.
A driven adapter is called by the business layer, to interact with some backend tool, ie. a database, storage bucket, cache, etc. The business layer calls into the driven adapter by way of the Port. Driven adapters implement the Port defined by the business layer.
So the flow generally looks like
Driving Adapter <--> Port <--> Business Layer <--> Port <--> Driven Adapter
Benefits โ
Because the business layer enforces the Ports, and all external interactions with external components happens through the Ports, the business layer is encapsulated. The benefits of this for the system cannot be overstated.
It means the business layer, the models and the rules governing them, can be developed before choosing things like a database, or a frontend framework. Better yet, the business layer can be tested before choosing any of those things. Covering business logic almost entirely with unit tests is a boon for confidence in the system.
When building software, a decision should be conceptualized as a set of constraints, and ideally the pros of accepting those constraints outweigh the cons. So we ought to defer accepting constraints until we have as much information as possible to inform that decision. Clean Architecture enables us to do just that.
It also allows for the separation of concerns. Each tier of the architecture can change without requiring changes in the other tiers. This is important because some tiers are more volatile than others; typically the UI of an application changes faster than business rules for example.
The only time we must touch multiple tiers is if a Port is changed. And that is usually a find and replace
hyper Lingua Franca ๐ โ
hyper Apps ๐ฎ โ
The Driving Adapters in the hyper Service Framework are called Apps
or Apis
. The hyper Core team maintains an HTTP-based RESTful hyper App
implementation, using the popular Web Server framework express. You can see the API Reference here
In the future, we would like to see more app offerings. A CLI app, a GRPC app, we've even discussed a Service Worker app, so that hyper could be run entirely in the browser, on a Service Worker ๐.
hyper Core ๐ชจ โ
The business layer in hyper is called core which contains all of the business logic and enforces each Port. Core also defines the Port that a hyper App
invokes.
hyper Ports ๐ โ
There are multiple Ports defined in the hyper Service Framework. The current ones are Data
, Cache
, Storage
, Queue
, and Search
.
hyper Adapters ๐ โ
The Driven Adapters in The hyper Service Framework are simply called Adapters
. You can see many of them that the Core team has implemented here. Each adapter implements a Port
and does the heavy lifting of communicating with an underlying technology being used to power the Port
.
hyper Services โ
A hyper Service
is combination of the components described above:
- A hyper
App
- hyper Core
- An
Adapter
Port - A hyper
Adapter
For example, a hyper Data
Service might be:
RESTful api -> Core -> Data Port -> CouchDB Adapter
// or
GraphQL api -> Core -> Data Port -> Postgres Adapter
// or
CLI -> Core -> Data Port -> DynamoDB Adapter
A hyper Cache
Service might be:
GRPC api -> Core -> Cache Port -> Redis Adapter
// or
GraphQL api -> Core -> Cache Port -> Sqlite Adapter
// or
CLI -> Core -> Cache Port -> FlatFile Adapter
A hyper Search
Service might:
GRPC api -> Core -> Search Port -> Elasticsearch Adapter
// or
RESTful api -> Core -> Search Port -> Minisearch Adapter
// or
CLI -> Core -> Search Port -> Algolia Adapter
I hope you're noticing something:
Adapters
and Apps
are interchangeable. This is how we can test our business layer without choosing a database, for example. We can simply stub the Adapter
that implements the Port
that the core business logic interacts with.
hyper Server โ
A hyper Server
is an instance that hosts multiple hyper Services
and may be consumed via whatever the hyper App
api exposes.
hyper Domain โ
hyper Services
are created on a hyper Server
, within a hyper Domain
.
A Domain
is simply a logical grouping of hyper Services
hosted on a hyper Server
.
hyper Domains
are commonly used to distinguish a set of hyper Services
leveraged by an application. For example, if you had foo
application and a bar
application, they each might have their own Domain
and own connection string to the hyper Server
:
const connect = require("hyper-connect");
const DOMAIN = "foo";
const { data, cache } = connect(`https://${SUB}:${SECRET}@HOST/${DOMAIN}`);
// Create a Data Service and Cache Service in the foo domain
await data.create();
await cache.create();
// #############################
// In another deployable, or perhaps a logically-separated
// part of the same deployable ie. Modular-Monolith
// ##############################
const DOMAIN = "bar";
const { data, storage } = connect(`https://${SUB}:${SECRET}@HOST/${DOMAIN}`);
// Create a Data Service and Storage Service in the bar domain
await data.create();
await storage.create();
INFO
A Connection String
used with hyper-connect
is ALWAYS tied to a single hyper Domain
. If you need to consume multiple Domains
in the same context, simply instantiate multiple instances of hyper-connect
, like in the example above.