返回目录

用“接口加实现”的分层组织后端代码

想解决的问题

后端代码最容易写成的样子是:业务逻辑里直接调数据库、直接调第三方接口。刚开始写得快,后面有两件事会很难受:

这种分层的名字叫“端口和适配器”,说白了就是:业务代码只认接口,具体用什么实现,最外面再接上。

目录怎么分

core/
  ports/       接口:要存什么、要查什么、要调什么外部能力
  usecases/    业务规则:只 import ports,不碰任何具体实现
infra/
  some-db/     接口的具体实现:某个数据库、某个云服务
container.ts   把具体实现装进业务规则里,整个程序只有这一处知道用的是谁

一个小例子

// core/ports/record-repo.ts
export interface RecordRepo {
  add(userId: string, record: NewRecord): Promise<Record>;
  listByDay(userId: string, day: string): Promise<Record[]>;
}

// core/usecases/add-record.ts
export async function addRecord(
  deps: { records: RecordRepo; clock: Clock },
  userId: string,
  input: RecordInput,
): Promise<Record> {
  // 校验、计算、业务规则都在这里,看不到任何数据库代码
  const record = buildRecord(input, deps.clock.now());
  return deps.records.add(userId, record);
}

连时间都做成接口(Clock),是因为“今天是哪天”会影响很多规则。测试里能把时间固定住,跨天、跨月的情况都好测。

带来的好处

要注意的