文章

洋葱架构、事件驱动与 GitLab 架构模式对照

讲解洋葱架构、事件驱动、CQRS、分层架构等概念,并与 GitLab 模块化单体中的 pragmatic 实现对照。

洋葱架构、事件驱动与 GitLab 架构模式对照

本文补充 整洁架构、六边形架构、DDD 战略模式,覆盖其他常见架构术语。

1. 架构模式关系图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
                    ┌─────────────────┐
                    │   DDD           │  战略:Bounded Context、Event
                    │  (战略+战术)     │  战术:Entity、Aggregate、Service
                    └────────┬────────┘
                             │
        ┌────────────────────┼────────────────────┐
        ▼                    ▼                    ▼
┌───────────────┐  ┌─────────────────┐  ┌─────────────────┐
│ Layered       │  │ Clean / Onion   │  │ Hexagonal       │
│ (经典三层)     │  │ (同心圆+依赖向内) │  │ (Ports/Adapters)│
└───────────────┘  └─────────────────┘  └─────────────────┘
        │                    │                    │
        └────────────────────┼────────────────────┘
                             ▼
                    ┌─────────────────┐
                    │ Event-Driven    │  GitLab: EventStore
                    │ CQRS (读写分离)  │  GitLab: Finder vs Service
                    │ Modular Monolith│  GitLab: bounded_contexts.yml
                    └─────────────────┘

2. 洋葱架构(Onion Architecture)

Jeffrey Palermo 提出,与 Clean Architecture 几乎同构:Domain 在最中心,Infrastructure 在最外。

2.1 层次(内 → 外)

层内容GitLab
Domain ModelEntity、Value Object、Domain Serviceapp/models/、PORO
Domain Services跨 Entity 的领域逻辑Entity 方法、少量 lib/gitlab/
Application ServicesUse Case 编排app/services/<context>/
InfrastructureDB、MQ、外部 APIActiveRecord、Gitaly、Sidekiq

2.2 与 Clean Architecture 的唯一强调点

洋葱架构更明确区分 Domain Service vs Application Service:

类型职责GitLab 实例
Domain Service无状态领域逻辑,不属于单个 EntityGitlab::UserAccess(权限计算)、Releases::TriggeredHooks
Application Service编排、事务、副作用Releases::CreateService
1
2
3
4
5
6
7
8
9
# Domain Service 风格 — 封装跨 Entity 逻辑
# app/models/projects/triggered_hooks.rb
module Projects
  class TriggeredHooks
    def execute
      @relations.each { |hooks| hooks.hooks_for(@scope)... }
    end
  end
end
1
2
3
4
5
6
7
# Application Service — 编排完整用例
# app/services/releases/create_service.rb
def execute
  return error unless allowed?
  tag = ensure_tag
  create_release(tag, evidence_pipeline)
end

GitLab 不严格区分 目录;两者都在 app/models/ 或 app/services/,靠命名和职责区分。


3. 经典分层架构(Layered Architecture)

1
2
3
4
Presentation  →  Controller / API / GraphQL
Business      →  Service
Persistence   →  ActiveRecord / Finder
Database      →  PostgreSQL

GitLab 的映射与偏离

经典层GitLab偏离
Presentationapp/controllers/、lib/api/不受 BC 约束
Businessapp/services/称 Use Case 更准确
Persistenceapp/models/(AR 混合了 Domain+Persistence)Active Record 模式:Entity 即 ORM
Databasedb/分片、Cells

Active Record 模式(Fowler):Entity 类同时负责持久化,Domain 与 Infrastructure 未分离——GitLab 的主要 pragmatic 妥协。


4. 事件驱动架构(Event-Driven Architecture)

4.1 概念

组件通过 事件 异步通信,降低时间耦合。

4.2 GitLab EventStore

官方动机(doc/development/event_store.md):消除 PostReceive 等 God Worker 的跨域耦合。

Without EventStore(紧耦合):

1
2
Ci::CreatePipelineService ──perform_async──▶ MergeRequests::UpdateHeadPipelineWorker
                         ──perform_async──▶ Namespaces::Onboarding::Worker

With EventStore(事件驱动):

1
2
3
4
5
Ci::CreatePipelineService ──publish──▶ Ci::PipelineCreatedEvent
                                           │
              ┌────────────────────────────┼────────────────────────────┐
              ▼                            ▼                            ▼
   MergeRequests::UpdateHeadPipelineWorker   Onboarding Worker   ...

4.3 事件类型

类型GitLab
Domain EventProjects::ProjectCreatedEvent、Ci::PipelineCreatedEvent
Integration Event同上,通过 Sidekiq 跨 BC 传递
Event NotificationWebhook(HookData::ReleaseBuilder)— 对外部系统

4.4 实例:Pipeline 创建

1
2
3
4
5
6
7
# 发布方 — app/services/ci/create_pipeline_service.rb
Gitlab::EventStore.publish(
  Ci::PipelineCreatedEvent.new(data: {
    pipeline_id: pipeline.id,
    partition_id: pipeline.partition_id
  })
)
1
2
# 订阅方 — lib/gitlab/event_store/subscriptions/merge_requests_subscriptions.rb
store.subscribe ::MergeRequests::UpdateHeadPipelineWorker, to: ::Ci::PipelineCreatedEvent

发布方 不知道 有哪些订阅者——开闭原则。


5. CQRS(Command Query Responsibility Segregation)

5.1 概念

写(Command) 与 读(Query) 分离:不同模型、不同路径。

5.2 GitLab 的轻量 CQRS

GitLab 没有 独立的 Read Model 数据库,但有清晰的 读写分离:

 Command(写)Query(读)
入口REST POST/PUT/DELETE、GraphQL MutationREST GET、GraphQL Query
编排app/services/*/ Use Caseapp/finders/ Finder
返回ServiceResponse、side effectsActiveRecord::Relation、预加载
副作用Webhook、Worker、Audit无
1
2
3
4
5
# Command
Releases::CreateService.new(project, user, params).execute

# Query
ReleasesFinder.new(project, user, params).execute

5.3 读模型 Adapter

查询结果经 Serializer / Entity 转为 API 响应(Presentation 层读模型):

1
present releases, with: Entities::Release

不是 经典 CQRS 的独立投影表,而是 同库读写 + 职责分离。

5.4 何时 GitLab 接近完整 CQRS

  • Value Stream Analytics 等分析后端使用聚合表,不直接查 Issue/MR 核心表(doc/development/value_stream_analytics/)
  • ClickHouse 分析(EE)— 独立读存储

6. 模块化单体(Modular Monolith)

6.1 概念

单一部署单元,内部按模块/上下文划分,兼顾单体运维与模块化开发。

6.2 GitLab 实现

机制路径
Bounded Context 注册config/bounded_contexts.yml
命名空间隔离Ci::、Releases::、MergeRequests::
跨模块通信EventStore、少量 Service 调用
未来 Cells按 Organization 分片

与微服务对比:

 Modular MonolithMicroservices
部署一个 GitLab 实例多服务
边界Ruby namespace网络 + 独立 DB
GitLab 选择✓ 当前方向部分提取(Gitaly、Workhorse)

已拆分的基础设施服务:Gitaly(Git)、Workhorse(HTTP 代理)— 作为 Secondary Adapter 独立进程。


7. 内外层依赖关系(概要)

洋葱 / 整洁 / 六边形共享 依赖向内 规则。GitLab 通过 调用方向 体现,而非独立 domain/ 目录。

1
2
3
4
5
6
7
8
9
10
外层 Adapter(api/controllers/workers)
        │ 只向内调用
        ▼
Application Service(app/services/)
        │
        ▼
Domain Model(app/models/)
        │ 经 Gateway/Builder 访问
        ▼
Infrastructure(Gitaly、Sidekiq、PostgreSQL)

详细分析见:


8. 设计模式在 GitLab 中的体现

GitLab 未系统标注 GoF 模式名,但源码中广泛存在。下表按 用途 归类,并给出可跳转的源码路径。

8.1 创建型模式

模式作用GitLab 实例路径
Factory Method按上下文创建不同实现ExportStatus.for_context 选 Online/Offlineapp/models/import/export_status.rb
Factory Method导入时 find-or-createImportExport::Project::ObjectBuilderlib/gitlab/import_export/project/object_builder.rb
Builder分步构建复杂 DTOHookData::ReleaseBuilder#buildlib/gitlab/hook_data/release_builder.rb
Singleton(框架级)Rails 单例组件Gitlab::Redis、Gitlab::CurrentSettingslib/gitlab/
1
2
3
4
5
# Factory Method — 调用方只依赖抽象
Import::ExportStatus.for_context(tracker, relation)

# Builder — Entity → Webhook Hash
Gitlab::HookData::ReleaseBuilder.new(release).build('create')

8.2 结构型模式

模式作用GitLab 实例路径
Adapter协议/技术适配REST API → Service;Gitaly → Git::Repositorylib/api/、lib/gitlab/git/repository.rb
Facade简化复杂子系统Gitlab::Git::Repository 隐藏 gRPClib/gitlab/git/repository.rb
Facade聚合根统一入口Project delegate → ProjectSettingapp/models/project.rb
Decorator/Presenter扩展展示而不改 EntityLinkPresenter#direct_asset_urlapp/presenters/releases/link_presenter.rb
Composite树形结构统一处理Work Item Widget 体系app/models/work_items/widgets/
1
2
3
4
5
6
7
8
9
10
11
12
# Adapter — Gitaly 适配
module Gitlab::Git
  class Repository
    def commit(sha); end  # 内部 gRPC,外部领域 API
  end
end

# Presenter — 展示层 Decorator
def direct_asset_url
  return @subject.url unless @subject.filepath
  release.download_url(@subject.filepath)
end

8.3 行为型模式

模式作用GitLab 实例路径
Command封装请求为对象CreateService#executeapp/services/releases/create_service.rb
Template Method基类定义骨架,子类填充BaseService 共享 milestone/tag 逻辑app/services/releases/base_service.rb
Strategy算法可替换Import::Offline::ExportStatus vs BulkImports::ExportStatusapp/models/import/
Observer状态变化通知EventStore 订阅 Workerlib/gitlab/event_store/subscriptions/
Mediator多对象通过中介通信Gitlab::EventStore 解耦发布/订阅lib/gitlab/event_store/store.rb
State对象随状态改变行为MergeRequests::MergeData state machineapp/models/merge_requests/merge_data.rb
Chain of Responsibility请求沿链传递Policy 规则链 DeclarativePolicyapp/policies/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Command
module Releases
  class CreateService
    def execute; end  # 统一入口
  end
end

# Observer + Mediator
Gitlab::EventStore.publish(Ci::PipelineCreatedEvent.new(...))
store.subscribe MergeRequests::UpdateHeadPipelineWorker, to: Ci::PipelineCreatedEvent

# Template Method — 子类未实现则抛错
def in_progress?
  raise Gitlab::AbstractMethodError
end

8.4 架构 / 领域模式(非 GoF)

模式作用GitLab 实例
Repository(变体)持久化抽象ActiveRecord + Release.find(无独立 Repository 类)
Query Object复杂只读查询ReleasesFinder#execute
Unit of Work事务边界ApplicationRecord.transaction in UpdateService
Anti-Corruption Layer外部模型翻译GithubImport::Representation::Issue.from_api_response
Domain Event跨 BC 异步事实Projects::ProjectCreatedEvent
Service Layer用例编排app/services/<context>/
Data Transfer Object跨层数据传输lib/api/entities/release.rb(非 Domain Entity)

8.5 设计模式与架构层对应

架构层常见模式
EntityState、Strategy(enum)
Application ServiceCommand、Template Method
FinderQuery Object
Primary AdapterAdapter
Secondary AdapterAdapter、Builder、Facade
跨 BCObserver、Mediator、Domain Event
Import 边界ACL、Factory、Strategy

8.6 SOLID 与设计模式的关系

原则模式支撑GitLab 实例
S 单一职责Command 拆分用例Links::CreateService 独立于 CreateService
O 开闭Observer/Event新 Worker 订阅 Event,不改 CreatePipelineService
L 里氏替换StrategyOffline::ExportStatus 可替换 ExportStatus
I 接口隔离Event Schema只暴露 pipeline_id 等必要字段
D 依赖倒置Port/EventService publish Event,不依赖具体 Worker

9. 其他相关概念

9.1 Anti-Corruption Layer(防腐层)

见 DDD 战略模式:Gitlab::GithubImport::Representation::*。

9.2 Gateway / Facade

模式GitLab
GatewayGitlab::Git::Repository 封装 Gitaly
FacadeProject delegate 到 ProjectSetting

9.3 Repository 模式

GitLab 基本不用 classic Repository 接口:

1
2
Release.find(id)
ReleasesFinder.new(...).execute

ActiveRecord + Finder 是 pragmatic 替代。


10. 架构模式对照总表

模式核心思想GitLab 实现程度典型路径
Layered分层部分(AR 混合层)controllers → services → models
Clean依赖向内部分services 不依赖 api
OnionDomain 中心部分models + services
HexagonalPorts/Adapters较好api/controllers ↔ services ↔ git/gitaly
DDD 战略Bounded Context推进中bounded_contexts.yml
DDD 战术Entity/Aggregate部分models、Event
Event-Driven事件解耦较好(新代码)EventStore
CQRS读写分离轻量Service vs Finder
Modular Monolith模块单体官方方向namespace + Event

11. 如何选择阅读路径

你想理解…读哪篇
Release 完整分层Release 文档
Project 的 DDD 战术Project DDD
限界上下文与 EventDDD 战略
依赖规则 + 模式分层整洁架构 §7–§8
Port/Adapter + 模式六边形架构 §6–§7
GoF 模式总表 + CQRS本文 §8

12. 关键路径速查

概念路径
EventStorelib/gitlab/event_store/、doc/development/event_store.md
订阅注册lib/gitlab/event_store/subscriptions/
Commandapp/services/
Queryapp/finders/
Modular MonolithHandbook + config/bounded_contexts.yml
Import ACLlib/gitlab/github_import/representation/
Git Gatewaylib/gitlab/git/repository.rb
软件设计指南doc/development/software_design.md

13. 一句话总结

GitLab 不是某一种架构的教科书实现,而是 Layered + DDD Modular Monolith + Hexagonal Adapters + Event-Driven 解耦 + 轻量 CQRS 的组合;新代码趋向 Use Case、Finder、EventStore,legacy 仍保留 Active Record 与上帝对象,正在逐步治理。

本文由作者按照 CC BY 4.0 进行授权