5.1 项目架构设计


文档摘要

5.1 项目架构设计:把数据纪律落成结构 项目架构解决代码往哪放、依赖朝哪流的问题。本节给出经过实战检验的三层架构(数据访问层 / 领域状态层 / 表现层),核心主张:第 2.9 节的数据纪律不能靠口头约定,要靠目录结构、注入令牌与类型边界强制执行——结构即约束。 承接与目标 前四章攒下的机制与工具,从这一节开始收进真实工程的骨架里,先立结构再谈编码: 画出三层架构图并说明每层的职责与禁止事项。 用 InjectionToken 隔离数据访问层,让表现层无法绕过仓储直连接口。 制定目录约定与依赖方向规则。 判断"该放哪一层"的四个典型争议场景。 一、三层架构 层间规则三条:依赖只能朝下(features 可依赖 state 与 data 的令牌,state 可依赖 data,反向禁止);

5.1 项目架构设计:把数据纪律落成结构

项目架构解决代码往哪放、依赖朝哪流的问题。本节给出经过实战检验的三层架构(数据访问层 / 领域状态层 / 表现层),核心主张:第 2.9 节的数据纪律不能靠口头约定,要靠目录结构、注入令牌与类型边界强制执行——结构即约束。

承接与目标

前四章攒下的机制与工具,从这一节开始收进真实工程的骨架里,先立结构再谈编码:

  1. 画出三层架构图并说明每层的职责与禁止事项。
  2. 用 InjectionToken 隔离数据访问层,让表现层无法绕过仓储直连接口。
  3. 制定目录约定与依赖方向规则。
  4. 判断"该放哪一层"的四个典型争议场景。

一、三层架构

src/app/ ├── core/ 核心层:认证、拦截器、全局服务(单例) │ ├── auth.service.ts │ └── http-interceptors.ts ├── data/ 数据访问层:仓储实现(唯一允许知道接口细节的层) │ ├── order.repository.ts │ └── tokens.ts 依赖令牌:表现层只见令牌不见实现 ├── state/ 领域状态层:仓库(store)、信号、派生计算 │ ├── order.store.ts │ └── cart.store.ts ├── features/ 表现层:按业务域组织的页面与组件 │ ├── orders/ │ └── reports/ └── shared/ 共享层:哑组件、管道、通用指令(不含业务状态) └── ui/

层间规则三条:依赖只能朝下(features 可依赖 state 与 data 的令牌,state 可依赖 data,反向禁止);表现层不出现接口地址(HTTP 细节封死在 data 层);shared 无状态(有状态的服务一律上移到 state 或 core)。这三条规则防的正是主线上的头号敌人:绕过状态层直连接口导致的数据双份、原地修改导致的更新失灵。

二、用令牌隔离数据层

// data/tokens.ts:定义仓储接口与令牌 import { InjectionToken } from '@angular/core'; import { Observable } from 'rxjs'; export interface OrderRepository { list(kw: string): Observable<Order[]>; save(draft: OrderDraft): Observable<Order>; } export const ORDER_REPOSITORY = new InjectionToken<OrderRepository>('订单仓储'); // data/order.repository.ts:实现(唯一知道接口细节的地方) export class HttpOrderRepository implements OrderRepository { private http = inject(HttpClient); list(kw: string) { return this.http.get<Order[]>('/api/orders', { params: { kw } }); } save(draft: OrderDraft) { return this.http.post<Order>('/api/orders', draft); } } // 应用配置中绑定 providers: [ { provide: ORDER_REPOSITORY, useClass: HttpOrderRepository } ] // state/order.store.ts:状态层消费令牌(3.3 节测试时可换成内存假实现) export class OrderStore { private repo = inject(ORDER_REPOSITORY); private _list = signal<Order[]>([]); readonly list = this._list.asReadonly(); load(kw: string) { this.repo.list(kw).subscribe(rows => this._list.set(rows)); } }

令牌隔离的直接回报在测试(3.3 节):换一个内存实现注入,状态层测试不打网络。间接回报是编译期的防绕行——表现层注入 ORDER_REPOSITORY 拿到的是接口类型,接口上没有的便捷方法想调也没有。

三、争议场景对号入座

场景 错放 正放 判断依据
订单状态徽标管道 features/orders shared 无状态纯展示,别的域也要用
当前用户信息 features/profile core 全局会话状态
列表筛选词 state 仓库 路由查询参数 URL 状态(2.9 节分类)
接口分页参数拼装 store data 层 属接口细节

图:依赖方向与数据流向全景

图:依赖方向与数据流向全景

四、案例:改造一个"面条项目"

背景:接手的旧项目没有分层:组件里直接发 HTTP、五十处接口地址散落各模板类、同一份用户数据在三个组件各存一份,界面时对时错(第 2.9 节描述过症状)。
操作:三周渐进改造——第一周建 data 与 state 层骨架,接口地址集中收编进仓储;第二周三个重复的用户数据存放点合并为一个信号仓库,组件改为订阅;第三周表现层清理,注入改走令牌。
结果:接口地址从五十处收敛到一处;"数据串了"类工单清零;之后一次后端接口整体换路径前缀,只改仓储一处上线。
解读:架构改造的收益账要这么算——前期投入换的是变更影响半径的缩小;数据纪律落成结构后,新人不需要读过本书也不会犯绕行错误,因为编译器替你拦着。
变式:若项目更大,先从最痛的一个业务域开始试点(一个域全套三层),验证约定后再横向铺开——比"全项目停业务整顿"现实得多。

本节要点回顾

  • 三层职责:data 管接口细节、state 管业务数据唯一存放、features 管表达;依赖单向朝下。
  • 令牌隔离:表现层只见接口不见实现,测试换实现零成本,绕行被编译器拦截。
  • shared 无状态:状态上移,哑组件下沉,边界清晰可复用。
  • 结构即约束:数据纪律写进目录与类型,比文档可靠。
  • 主线上的一环:分层保证"一份数据一个存放点、更新必换引用"在团队规模下长期成立。

下一节处理规模问题:大型应用的拆分与协作。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U