大数跨境
0
0

DDD中API设计

DDD中API设计 摩尔线程
2024-02-09
0
导读:你好,我是Alan, 上一篇文章我介绍了UML类图, 今天这篇文章主要分享一下关于DDD中的API设计。

你好,我是Alan, 上一篇文章我介绍了UML类图, 今天这篇文章主要分享一下关于DDD中的API设计。

什么是领域驱动设计这篇文章中我提到, 领域驱动设计的核心是建立起与业务强相关的模型, 来帮助我们更好的应对软件中的不确定性, 而API作为软件对外的接口, 是否可以和领域模型一样稳定呢? 那么由领域模型获得API就变得理所应当了

如何根据领域模型获得高质量的API呢? 这就要回到模型本身上去, 在事件风暴这篇文章中, 我提到业务方总是习惯从交互的角度去理解问题, 而模型更偏向于数据, 上面说API是与模型关联的, 那么自然API也从数据角度出发, 而RESTful API就是一个不错的选择。

首先我们需要弄清楚, 如何将模型与RESTful API相关联? 一共有四个步骤:

  1.  使用URL来表示领域模型

  2.  根据URL设计API

  3.  使用分布式超媒体设计API中涉及到的资源

  4.  使用得到的API去覆盖业务流程


第一步是通过URL来表示领域模型, 比如现在我们现在有这样一段URL, fnmain.cn/users/alan/subscription/, 那这段url背后的模型是什么呢?

这段URL其实对模型的细化, user-id是user的实例, subscription-id是subscription的实例。

下面我们通过一个更加复杂的模型来设计我们的API接口, 下面是对应的模型。

分别从两个聚合根上看, 分别是user和author。

首先是以user为聚合根的URL。

  1. /users/    user聚合根, 表示所有用户

  2. /users/{user_id} user实例化, 表示通过user_id标识某个用户

  3. /users/{user_id}/subscriptions, User-subscriptions聚合, 表示某个用户的全部订阅。

  4. /users/{user_id}/subscriptions, User-subscription聚合, 表示某个用户的全部订阅。

  5. /users/{user_id}/subscriptions/{subscription_id},User-Subscription的实例化, 表示通过user_id和subscription_id标定某个订阅。


其次是以author为聚合根的URL

  1. /authors/ author聚合根, 表示所有作者

  2. /authors/{author_id} author的实例化, 表示某个具体的作者

  3. /authors/{author_id}/columns/{column_id}, 作者撰写专栏的实例化, 表示每个特定的专栏。

  4. /authors/{author_id}/columns/{column_id}/contents/表示某个专栏的全部内容。

  5. /authors/{author_id}/columns/{column_id}/contents/{content_id}表示某个专栏内容的实例化, 表示某个特定的课程。

根据URL来设计API, 我们可以设计一个表格, 第一行的内容是角色, HTTP方法, URL, 业务场景。

角色

HTTP方法
URL
业务场景
{user_id}
GET
/users/{user_id}/
subsciptions
查看订阅文章详情
{user_id}
POST
/users/{user_id}/
sub
scriptions
订阅文章
{user_id}
PUT
/users/{user_id}/
sub
scriptions
将文章赠送它人
{user_id}
DELETE
/users/{user_id}/
subscriptions
管理员退订


到这里我们只是得到一组API候选, 接下来就需要使用分布式超媒体设计API中所涉及的资源, 用来完善API的设计, 以及使用得到的API去覆盖业务流程, 验证API的有效性。

首先我们需要知道什么样的格式属于超媒体, 其实很简单, 包含超链接的格式都可以看成是超媒体, 因此HTML是超媒体格式, 它的构成要素主要有两个分别是:

  • 指向关联资源的链接(href)

  • 与主资源是哪种关联关系(rel)


而我们常用的JSON和XML并没有提供默认的链接方式, 所以它们并不是超媒体, 我们在设计RESTful API的时候, 要使用HAL, 而不是原味的JSON或者XML。 

XML的HAL基本上就是把HTML的link作为标准标签引入了XML, 而JSON的HAL则定义了_links结构来表示链接。

下面我放了一段xml中的HAL

Content-Type: application/hal+xml
<resource rel="self" href="/orders/523">
  <link rel="warehouse" href="/warehouse/56"/>
  <link rel="invoice" href="/invoices/873"/>
  <currency>USD</currency>
  <status>shipped</status>
  <total>10.20</total>
</resource>

然后下面这段是JSON的

Content-Type: application/hal+json
{
 "_links": {
   "self": { "href""/orders/523" },
   "warehouse": { "href""/warehouse/56" },
   "invoice": { "href""/invoices/873" }
},
"currency""USD",
"status""shipped",
"total"10.20
}

说完了格式问题, 我们来看看如何使用超媒体设计API中的资源, 如何通过超链接构成渐进式服务消费。

首先我们看一下聚合根的超媒体描述, 对于一个User实例而言, 我们可以使用如下的HAL JSON去描述。

{
    "_links": {

        "self": {"href""/user/19"},
        "subscriptions": {"href""/user/19/subscriptions"}
    },
    "username""alan"
}

这段HAL JSON中包含两个超链接: self和subscriptions,self表示获取当前资源的URL, 我们会将这个URL称作主URL。这个URL从概念上讲和ID是等价的, 也就是可以用于唯一定位当前资源的标识符, 同时self也是用于缓存的URL。

subscriptions表示聚合关系User-subscriptions, 也就是指示了如何寻找被当前User聚合的Subscription, 对于聚合根而言, 需要为所有的聚合对象提供链接, 用来指示如何获取这些聚合对象。

你可能会问, self URL有什么用, 如果单独对一个User实例而言

{
    "_links": {
        "self""/users"
    },

    "_embedded": {
        "users": [
            {
                "_links": {
                    "self""/users/18"
                },
                "username""Alan"
            },

            {
                "_links": {
                    "self""/users/19"
                },
                "username""join"
            },
        ]
    },

    "total"4525
}

我们通过_embedded表示所有的用户, 但是有一个地方需要我们注意的是,如果User对象有很多属性, 那么我们需要在/users的HAL JSON中包含这些属性吗?

这个时候就该列表中每一个User对象中包含的self URL发挥作用了, 我们可以把集合资源和其中的独立资源, 看成是渐进式的两种服务。

也就是说, 我们可以把集合资源中包含的数据, 看作是更基础的服务, 像常用数据这样, 可以满足大部分客户端在通常情况下对用户数据的需求, 而其中每一个user对象中包含的self URL所指示的服务, 我们可以看成是增强服务, 也就是全量服务。

于是, 如果某个客户端需要不包含常用数据中的信息, 那么它可以通过self URL去获取全量数据, 也就是说, 我们将常用数据和全量数据设计成为渐进式消费的两种不同的服务, 并通过分布式超媒体格式, 描述它们之间的关联。

构造资源的时候, 将缓存当作必须考虑的特性, 详细进行设计。

当然我们还需要进行分页处理, 比如前面/users一共有43215个用户, 如果将所有的用户都包含进来, 就会产生极大的HAL JSON文件, 因此我们需要分页处理, 于是就变成了下面这种。

"_links": {
   "self""/users",
   "next""/users?page=3"
}

我们可以通过next链接表示后面这一页, 那么如何通过缓存, 使得当有新用户注册的时候, 大部分的缓存都不失效呢?

一个比较有效的策略是, 永久不缓存/users页面, 也就是/users永远表示新注册的用户, 如果分页达到上限的时候, 就生成新的页面id, 并进行缓存, 然后继续使用/users去等待新注册的用户

在我们将API中涉及的资源都使用分布式超媒体描述之后, 我们就获得了完整的API定义, 最后一步就是将API重新映射回所需要支撑的业务流程或用户使用场景中, 与业务方一起验证这些API是否能够满足所有的需求

好了, 到这里这篇文章就结束了, 这篇文章主要介绍了如何在DDD中构造出合适的API。

【声明】内容源于网络
0
0
摩尔线程
摩尔线程以全功能 GPU 为核心,致力于向全球提供计算加速的基础设施和一站式解决方案,为各行各业的数智化转型提供强大的AI计算支持。
内容 301
粉丝 0
摩尔线程 摩尔线程以全功能 GPU 为核心,致力于向全球提供计算加速的基础设施和一站式解决方案,为各行各业的数智化转型提供强大的AI计算支持。
总阅读131
粉丝0
内容301