This documentation introduces the Domain-Driven Design and the design of AppFlowy. Feel free to skip the first part if you know what Domain-Driven Design is.
Basic Concepts
AppFlowy Design
The most common architecture pattern is the layered architecture pattern, known as the n-tier architecture pattern.
Partition the software into layers
to reduce the complexity. Each layer of the layered architecture pattern has a
specific role and responsibility.
For many architects, the process of data modeling is driven by intuition. However, there are well-formulated methodologies for approaching it more formally. I recommend the Domain-Driven Design and choose it as AppFlowy architecture.
DDD consists of four layers.
┌──────────────────────────────────────────────────┐ ─────────▶
│ Presentation Layer │──┐ Dependency
└──────────────────────────────────────────────────┘ │
│ │
▼ │
┌──────────────────────────────────────────────────┐ │
│ Application Layer │ │
└──────────────────────────────────────────────────┘ │
│ │
▼ │
┌──────────────────────────────────────────────────┐ │
│ Domain Layer │◀─┘
└──────────────────────────────────────────────────┘
▲
│
┌──────────────────────────────────────────────────┐
│ Infrastructure Layer │
└──────────────────────────────────────────────────┘
▲
│
Level of ├───────────────────┐
Abstraction │ Presentation │
├───────────────────┴───────┐
│ Application │
├───────────────────────────┴─────────┐
│ Domain │
├─────────────────────────────────────┴────────┐
│ Infrastructure │
└──────────────────────────────────────────────┴─────▶
Complexity
Presentation:
Application:
Domain:
Infrastructure:
This layer acts as a supporting library for all the other layers. It deals with APIs, databases and network, etc.
DDD classifies data as referenceable objects, or entities, and non-referenceable objects, or values.
Entity
user, order, book, table. They are referenceable because they carry an identity which allows us to reference them.
Value
email, phone number, name, age, description. They can't be referenced. They can be only included into entities and serve as attributes. Values could be simple or could be composite.
Aggregate
entities can be grouped into aggregates. Aggregates can simplify the model by accessing th entire aggregate. For instance, Table has lots of row. Each row using the table_id to reference to the table. TableAggregate includes two entities: Table and the Row.
TableAggregate
┌────────────────────────────────────────────────────────────────┐
│ │
│ ┌────────────────────┐ ┌─────────────────────────┐ │
│ │struct Table { │ │struct Row { │ │
│ │ id: String, │ │ table_id: String, │ │
│ │ desc: String, │◀▶───────│} │ │
│ │} │ │ │ │
│ └────────────────────┘ └─────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────┘
DDD also introduces Service
and Repository
.
Service
When a significant process of transformation in the domain is not a natural responsibility of an Entity
or Value object
, add
an operation to the model as standalone interface declared as a Service. For instance: The Value object
, EmailAddress,
uses the function validateEmailAddress
to verify the email address is valid or not. Service
exists in Application, Domain and
Infrastructure.
class EmailAddress {
final Either<Failure<String>, String> value;
factory EmailAddress(String? input) {
return EmailAddress._(
validateEmailAddress(input),
);
}
const EmailAddress._(this.value);
}
Either<Failure<String>, String> validateEmailAddress(String? input) {
...
}
Repository
Repository offer an interface to retrieve and persist aggregates. They hide the database or network details from the domain. The Repository interfaces are declared in the Domain Layer, but the repositories themselves are implemented in the Infrastructure Layer. You can replace the interface implementation without impacting the domain layer. For instance:
// Interface:
abstract class AuthInterface {
...
}
// Implementation
class AuthRepository implements AuthInterface {
...
}
More often than not, the repository interface can be divided into sub-repository in order to reduce the complexity.
The AppFlowy Client consists of lots of modules. Each of them follows the DDD design pattern and using dependency injection to communication with other module.
Client
┌──────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ Module A Module B Module C │
│ ┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐ │
│ │ presentation │ Dependency │ presentation │ │ presentation │ │
│ │ │ Injection │ │ │ │ │
│ │ application ├───────────────▶│ application │ │ application │ │
│ │ │◀───────────────│ │ │ │ ◉ ◉ ◉ │
│ │ domain │ │ domain │ │ domain │ │
│ │ │ │ │ │ │ │
│ │ Infrastructure │ │ Infrastructure │ │ Infrastructure │ │
│ └───────────────────┘ └───────────────────┘ └───────────────────┘ │
│ ▲ │ │
│ │ Dependency Injection │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────────────────────────────────────┘
Let's dig it how can I construct each module. I take User
module for demonstration.
presentation Application domain Infrastructure
│
│ │
7 Data Model
┌──────────────────────────────┐ │ ┌────────────────────────┐ │ ┌───────────────────────────┐
│ │ │ │ ┌─────────────┐ │ ┌─────▶│ Repository implementation │
▼ Bloc │ 2.1 │ │ Aggregate │ │ │ │ └───────────────────────────┘
┌─────────────┐ │ ┌─────────────────┴─────┐ │ └─────────────┘ │ │ │ 4
│ Widget │ │ ┌────────┐ ┌────────┐ │ │ │ ┌────────┐ │ │ │ ▼
└─────────────┘ │ │ │ Event │ │ State │ │───┬────▶│ │ Entity │ │ │ ┌─────────────────────┐
│ │ └────────┘ └────────┘ │ │ │ │ └────────┘ │ │ │ │ Unit of Work │
│ │ └──────▲────────────────┘ │ │ ┌─────────────────┐ │ │ └─────────────────────┘
│ │ │ │ │ │ Value Object │ │ │ │ │ 5
└──────────┼────────┘ │ │ └─────────────────┘ │ │ ▼
1 │ │ └────────────────────────┘ │ │ ┌─────────────────────┐
│ │ ◈ │ │ Persistence Service │
│ │ │ contain │ │ └─────────────────────┘
│ │ ┌────────────────────┐ │
│ │ │ Service │ │ │
│ │ └────────────────────┘ │
│ 2.2 │ │
│ │ ┌───────────────────────┐ │
│ └───▶│ Repository interface │ │ │
│ └───────────────────────┘ │
│ │ │ │
│ │ 3 │
│ └───────────────┼──┘
Widget
. It should communicate through events.Domain
Repository are used to store the domain model data and calls respective service to finish the jobs.