adr-8: decided | Discussion |
decided | 2024-02-25 |
Architecture Update
A summary of the current architecture of the application
Decision
Hexagonal Architecture
The general philosophy of the project is still to follow a hexagonal architecture pattern. See ADR-02 for a more thorough discussion of what that means.
To quickly summarize: hexagonal architecture is a layered pattern. The core layer encapsulates all of the distinctive logic of the application. The outer layers can be thought of as either drivers or adapters. The core layer defines a number of adapter interfaces, and core services depend on those interfaces. Adapters implement those interfaces, to provide managed interaction with outside services, such as the database, message queues, clients, and so on. Some of these adapters will respond to external input and generally trigger behavior in the rest of the application. It's helpful to think of these as drivers. The Letterbook.Api
project is the primary example of a driver at this stage.
All of this relies on dependency injection to function. Some host process is responsible for managing the application lifecycle, and configuring the dependency injection provider. At this stage, that is also Aspnet Core, as configured in Letterbook.Api
. In the future, there may be additional configurations with multiple specialized hosts. In order to support easy health checks, those hosts will likely also be aspnet core applications.
Current State
*not implemented yet
So, generally, core logic services sit in the center of the architecture, and everything else depends on them, or implements an interface that core services depend on. This isolates the core logic from any concerns related to interacting with outside systems or infrastructure. And the adapters are agnostic to the specifics of the core logic. They will generally handle core data models, but mostly to serialize them. Just like with other dependencies, core services should only handle core data models. They should generally not handle serialization or DTOs.
Core Services
AccountService
manages actions related to accounts and identity. Signup, login, logout, password management, and contact methods would all be managed through the Account service.ProfileService
manages actions related to external social profiles. Follows, followers, blocks, and profile data like display names and profile pictures are handled through the Profile service.PostService
manages actions related to posting. Creating, sharing, and viewing posts and threads will generally rely on the Post service.TimelineService
builds feeds of recent posts. Viewing the home feed or a list would be handled by the Timeline service.AuthorizationService
encapsulates authorization logic for the rest of the application.
In addition, there are a variety of event and message services. These are generally pretty thin, and just exist to consistently schedule messages to an adapter.
Infrastructure
- Postgresql is the core database for Letterbook. This is the system of record, and it stores all of Letterbook's application data.
- Timescale is a specialty timeseries database. Letterbook uses it to provide a materialized view of post feeds (and soon, notifications). Timescale is actually a plugin for Postgres, so in many cases it's possible to run with only the one database server.
These are not yet implemented, but Letterbook will also depend on an S3-compatible object store, and an email service. Farther down the line, it's also likely we'll optionally incorporate a text search database like ElasticSearch.
Message Queues
In practice, ActivityPub requires some asynchronous message exchanges, often in large volumes. Like most other fediverse servers, Letterbook facilitates this by using message queues. At present, these are all in-memory Observables, provided by Rx.net. In future, this will optionally be provided by a dedicated external message queue, likely RabbitMQ or another AMQP service.
Future Services
There are some obvious categorical feature gaps that will require new services to cover. Which is to say, the software architecture is going to grow a lot over time. Hopefully a short time, at that. For example, we'll need
- file and media services
- notifications
- moderation
- web and streaming
- chat
- likely other things I'm forgetting at the moment
Discussion
Overlapping Scopes
Sometimes a feature could reasonably be provided by more than one service. For instance, Accounts and Profiles are closely related concepts. Posts and Profiles will both be very closely tied with moderation. It's reasonable to expect that some features at the boundaries of these services might not clearly belong to one service or the other. Resolving that ambiguity is more art than science. It may be tempting to have services depend on each other. That's usually good software engineering practice. But, there's some risk that could introduce redundant queries or API calls, so it's something that should be done with care. If possible, it's likely better to just choose one or the other of the services to perform that behavior. If that's not possible, then you should condsider refactoring the shared behavior into a dedicated class with minimal dependencies, and allow both services to use it from there.
PR Comments
See the original PR for some additional context and tangential discussion