使用GraphQL服务器(Apollo)+ TypeScript + DDD进行开发,感觉很不错!
意义
取得成功的目标
我喜欢使用graphql,在个人和小型项目中使用apollo server + typescript +DDD进行开发。虽然我还在学习中,但我感觉graphql server和DDD非常契合。因此,我想与那些想要构建或重构graphql server的人分享这个经验(尤其是那些在前端想要引入graphql却很难获得共鸣的人,希望能为他们提供一些参考资料哈哈)。这不仅适用于apollo server,我认为它可以在任何语言中使用(个人倾向于尝试gqlgen)。
抱歉
-
- 筆者はDDDのスペシャリストではありません
そもそもDDDとはなんぞやとか概念ついては深堀りしません。ネット上や Qiita にも多くの方が素晴らしい説明をされているのでそちらを参考にしてくださいm(_ _)m
コードのサンプルを載せますがこれがベストプラクティスとういわけではないです。DDD の実装では「間違いはあっても正解はない」という認識なので、「こうした方がいいんじゃね!?」ってあればドシドシコメントください
使用DDD + GraphQL有什么好处?
域名设计可以直接应用到架构设计中。
起初尝试使用 GraphQL 的时候,我觉得”这个东西挺好用的!”,但是到了一定阶段,schema和resolver经常变得非常混乱。就像在 React 中没有组件设计指南会导致混乱一样,没有schema的设计指南也会使得混乱不堪。

导致模式混乱的原因
-
- オブジェクトの分け方がわからなくなる
一貫性のないオブジェクトのまとめ方
オブジェクトの関係が複雑
一貫性のない field の型
UI または server の実装に依存している
为了解决这些问题,这个优秀的教程提到了领域和对象的关系。精简重点如下:
API 设计是一个具有挑战性的任务,需要反复改进、实验和对业务领域有足够的理解。
规则 #1:在深入细节之前,首先从高层次考虑对象及其关联性。
规则 #2:不将详细实现包含在API设计中。
规则 #3:在详细实现、用户界面或遗留API之外,根据业务领域进行API设计。
规则 #9:选择字段名称应基于有意义的概念,而不是历史或详细实现。
当我们进行几乎所有DDD和分层架构开发时,这些规则都是适用的吗?换句话说,当进行领域建模时,自然而然会确定大致的架构设计。
GraphQL的设计是将其置于表现层。
“来自Facebook的李拜伦先生说道:”
GraphQL 被设计为一个轻量级接口,紧密连接所有现有的系统。[视频链接]
总之,GraphQL被设计为一个表现层。因此,即使在将其应用于现有系统的情况下,也可以在不更改域逻辑的情况下引入。即使你觉得”GraphQL很烂!我想要使用gRPC!”,也可以进行相应的调整。稍后会解释,将GraphQL与业务逻辑连接起来的控制器也就是解析器。
顺便提一句,我非常喜欢上面的李先生的讲座,看了好几次呢哈哈,虽然是2016年的,但现在还非常有参考价值!
适用于CQRS的
在graphql的操作中,包含了
-
- Query
-
- Mutation
- Subscription
有一个特殊的subscription不考虑,在主要中我们主要使用Query和Mutation。Query是不带副作用的请求,而Mutation是带有副作用的请求。
这种思想与CQRS相似对吧!
通过将查询和变更的模型分离,可以更容易地处理复杂的查询,并且可以反映DDD的思想而无需担心写入操作。在graphql中,查询很容易变得复杂,所以使用CQRS既有利于DDD又有利于graphql!
实施的示例
以下是一个使用TypeScript和领域驱动设计的GraphQL服务器示例的GitHub链接:https://github.com/takumiya081/graphql-server-typescript-ddd-example
我创建了一个示例。为了更容易理解,我创建了一个仅包含获取用户的数据层,而没有业务逻辑的示例。
分层结构是基于clean architecture的。在进行分层的同时,我也注意到了依赖关系的方向。
此外,尽管我们在这个示例中使用了mysql,但我们没有解决N+1问题。请根据需要实现延迟加载,比如使用dataloader等。
目录结构
/src
├─ core
├─ controller // resolverなど
├─ infra
| ├─ server
| └─ database
└─ modules
└─ user
├─ domain
| ├─ model
| ├─ readModel
| ├─ repository // DIP用
| └─ query // DIP用
├─ useCases
| └─ createUser
└─ interface // clean architectureのgatewayと同じ
├─ query
└─ repository
积分
为了使用外部层的useCase,在依赖倒置原则中使用。
为了在useCase层使用query和repository,就需要在domain层定义它们各自的抽象。
需要将interface层和Database层分开吗?
在中文中对以下内容进行释义:
作为优点,当给仓库添加规则时更容易应对。
例如,假设我们希望在保存用户时将其保存到数据库并添加到Algolia中。
我们可以在接口层的UserRepository.create函数中利用定义在基础设施层的db和Algolia!
export class UserRepository implements IUserRepository {
private userDbModel: UserDBModel;
private userAlgoliaModel: UserAlgoliaModel;
constructor(userDbModel: UserDBModel) {
this.userDbModel = userDbModel;
}
public async create(user: User): Promise<void> {
await this.userAlgoliaModel.create(user);
await this.userDbModel.create(user);
}
}
作为缺点,就是会出现相同接口类型的情况。
例如,如果说“不,不,我们可以使用pubsub来处理algolia,所以没问题”,在这种情况下,就会产生这样的多余方法。
export class UserRepository implements IUserRepository {
private userDbModel: UserDBModel;
// これ意味があるのかな??
public async create(user: User): Promise<void> {
await this.userDbModel.create(user);
}
}
在这种情况下,Infra的数据库可能也可以实现IUserRepository接口。
当在使用resolver传递依赖时,需要使用context进行传递。
当我们想要使用apollo-server-testing或其他工具来测试解析器时,我们希望能够传递测试所需的依赖项!在这种情况下,我们通过传递给context来实现这一点。
// server.ts
const context: Context = {
dbModels: {
user: new UserDBModel(UserSequelizeModel),
},
};
const server = new ApolloServer({
typeDefs: importSchema('schema.graphql'),
resolvers: resolvers as any,
context: context,
});
// testClient.ts
import {ApolloServer} from 'apollo-server-express';
import {createTestClient} from 'apollo-server-testing';
export function createApolloTestClient(context: Context) {
return createTestClient(
new ApolloServer({
schema,
context,
}),
);
}
要点
如果不将API公开给外部,那么schema应该仅公开UI所需的部分。
GraphQL的模式很容易添加新内容,但修改或删除则需要非常复杂的流程。
我们抑制了“反正以后可能会用到,现在就先添加”的想法,只有在需要时才会添加字段。
YAGNI!
设计域名和模式时要考虑时间因素!
在推进域名和模式的设计过程中,我感到自己可能存在更好的设计,追求这个想法导致我浪费了很多时间…我现在后悔没有在开始设计之前设定好时间限制。此外,在实施过程中,我经常遇到无法按照最初考虑的层次顺利进行的情况。在进行大量实施之前,我认为最好先制作一个简单的原型来确定想法。
并不是说一定要使用DDD
随着时间的推移,GraphQL周边的工具越来越多。考虑到应用程序的规模和工时等因素,使用工具比使用DDD和架构来实现一切更为合适,当然也有适合使用工具的情况。
尽管如此,域名的概念绝对至关重要。
无论工具如何进化,设计 schema 的责任仍然落在工程师身上。如果 schema 的设计有错,无论使用什么工具,在某处都会感到困难。我认为领域知识也适用于客户端的存储管理,所以考虑到最基本的领域边界等问题是值得的。
最后
请问您觉得如何?因为我还在努力学习中,所以认为可能还有更好的方法,但因为在互联网上没有太多相关文章,所以我写下了这篇文章。
自从开始使用GraphQL后,我就再也回不去RESTful了,哈哈。
对于那些对于让服务器端兼容GraphQL API有些犹豫的人,我认为使用DDD来建立BFF也是一种方法!
在写这篇文章的时候参考了您的意见。非常感谢您。
以下是对提供的内容的中文释义:
1. https://qiita.com/APPLE4869/items/d210ddc2cb1bfeea9338
这是一个关于Web开发和编程方面的文章,提供了有关使用特定编程语言的技巧和教程。
2. https://www.slideshare.net/koichiromatsuoka/ddd-x-cqrs-orm
这是一份幻灯片,介绍了领域驱动设计(DDD)、命令查询职责分离(CQRS)和对象关系映射(ORM)的概念和实践。
3. https://khalilstemmler.com/
这是一个关于软件开发和设计模式的网站,提供了各种文章和资源,可以帮助开发者提升他们的编程技能和理解设计原则。