30-14 之 Domain Layer - Service

在谈论完书中提的组织 domain 的三种模式 :

  • Transaction Script
  • Domain Model
  • Table Module

接下来我们要来提书中将他放在 domain 章节的『 Service 』。

什麽是 Service 呢 ?

先提一下,我前面有几个范例里有写到 service,但严格来说这不是书中所提 service 层意思,之前范例是指商业逻辑用的地方,我范例都写 service,应该不少人的定义也是相同,但书中不同。

从书中抓出的一张图应该就可以知道, service 在书中实际上的位置如下,从下图可以简单的定义 :

Service Layer 会在 domain model 的前,但他还是属於 domain 层,它负责处理业务的工作流处理。

我当初在读 domain model 那章节我事实上一直有地方卡到,那就是 domain model 可以当做以每个业务为单位,然後内包属性与行为,那假设有个业务为『 这个月当用户注册後,要送 Coupon 卷 』,这个业务要如何处理呢 ?

以 domain model 应该是会分 :

  • User.register
  • Coupon.create

但问题是,同时呼叫这两个方法的地方是那 ? 对 ~ 这个地方就是 『 Service Layer 』。

https://ithelp.ithome.com.tw/upload/images/20210929/20089358rS4OQwfqbo.png
图片来源: 《 企业应用架构模式 》

书中事实上还有提到几个 Service Layer 的使用重点 :

  • 如果你的业务有多种用户。
  • 同时业务涉及多种事务操作

我事实上在思考 ~ 有时後我们 Web 开发时,有分所谓的 controller、service 会不会严格来说,以书中定义 controller ⇒ service 而 service ⇒ domain model 呢 ? 因为我相信不少人 controller 里面,是写这个 api 要处理的事情 ~ 对吧 ?

不过我这里不太建议,以我自已的标准这三个实际上该做的事情为 :

  • controller : 处理请求,并且将 service 回传的东西组装成这个 api 所需要的。
  • service : 处理整个业务所需要的工作流。
  • domain : 专门处理每个 domain 的工作内容。

范例

这里给个简单的范例,业务需求为 :

当用户注册时,会给一张 Coupon ( 折扣卷 ),且寄出欢迎信

简单来说该业务可以分为三个部份 :

  • 用户注册
  • 给 Coupon
  • 寄出欢迎信

其中有几点要注意 :

  • UserService 可以想成某个 app 要求使用者注册时,会首先开始处理的地方。
  • UserService 会继承 App Service,这里我是模仿书中范例写的,事实上不一定要这样做,这里可以想成 application service 是一个 container 然後可以从里面拿 『 这个 application 所提供的外部服务 』就好
  • 由於 email 服务为『 该 application 所提供的外部服务 』因此要从 ApplicationService 取得服候实体。
  • UserService 的 register 就是处理『 业务需求 』的整个工作流。
class ApplicationService {
    getEmailGateWay(): IEmailGateway{
        return new EmailGatewayService() 
    }
}

interface IEmailGateway{
    sendEmailMessage(toEmail: string, subject: string, body: any): void
}

class EmailGatewayService implements IEmailGateway{
    sendEmailMessage(toEmail: string, subject: string, body: any): void{
        console.log('取得该 application 的寄信服务的 config,然後在寄信')
    }
}

class UserService extends ApplicationService{
    register(username: string, email: string): void{
        const userDomainModel = new UserDomainModel({})
        const couponDomainModel = new CouponDomainModel({}) 
        // 建立使用者
        const user = new User(username, email)
        userDomainModel.create(user)
        // 建立第一次注册 Coupon
        const firstCoupon = new Coupon('First Coupon', 100)
        couponDomainModel.setCoupon(firstCoupon)
        couponDomainModel.sendToUser(user.id)

        // 寄欢迎信
        this.getEmailGateWay().sendEmailMessage('[email protected]', 'First Register',  {})
    }
}

class User{
    username: string
    email: string
    constructor(username: string, email: string){
        this.username = username
        this.email= email
    }
}

class Coupon{
    title: string 
    discount: number
    constructor(title: string, discount: number){
        this.title = title
        this.discount = discount
    }
}

class UserDomainModel{
    userDao: any
    constructor(userDao: any){
        this.userDao = userDao
    }
    create(user: User){
        this.userDao.create(user)
    }
}

class CouponDomainModel{
    couponDao: any
    coupon: Coupon
    constructor(couponDao: any){
        this.couponDao = couponDao
    }

    setCoupon(coupon){
        this.coupon = coupon
    }
    sendToUser(userId){
        console.log('Save to user coupon table')
        this.couponDao.save(this.coupon, userId)
    }
}

小总结

这个知识点可以用来解释什麽现象

有时後在写业务需求时,我发现真的很难每一个 domain 都定义清楚,像我刚刚提到的范例 :

当用户注册时,会给一张 Coupon ( 折扣卷 ),且寄出欢迎信

如果我们将『 用户注册时,会给一张 Coupon 』,写在 User Domain Model 中,但如果其它 application 不需要给一张 coupon 时,要怎麽办呢 ? 这时有了 service 专门处理工作流的地方真的方便不少。

这个知识点可以和以前的什麽知识连结呢 ?

事实上我写到现在,单纯的使用 domain model 来写,我还真想不到如果写,但多了一个 service layer 就真的方便多了…

然後在看《 搞笑谈软工-PoEEA之Server Layer 这篇文章 』》有提到一句话 :

传统阶层式架构所说的 Service Layer,就是Clean Architecture里面的 Use Case Layer。Teddy现在觉得Use Case Layer比较具体,因为Service这个字有很多种含意,因此用Service Layer来代表应用程序所提供功能或服务的边界,好像有点那麽不是很直觉

这句话我觉得有理,给其它人参考看看。

我要如何运用这个知识点 ?

  • 尝试更明确的思考,什麽时後写在 service layer,而什麽时後写在 domain layer。
  • 还有书中的 EmailGateway 的写法我觉得是不错的方向,这在之後的架构可以考虑这样拆。

参考资料


<<:  day29 : OPA规范k8s yaml(下)

>>:  29.移转 Aras PLM大小事-额外编码取号(3)

[Day 21] 2D批次渲染 (三) - Bug!一堆Bug

今日目标 继续完成批次渲染 结果... 今天抓到一堆bug,但是还是没debug完,被我弄丢的小方块...

Day 12 : 物件导向

在进入机器学习之前,想先大家深入了解一些 python 的进阶操作。接下来的操作会有点抽象,请好好品...

Day 5:认识CSS+CSS tag

在上一篇,我们学会如何用HTML写出'Hello World!',而这一篇,我将会教大家怎麽帮HTM...

企划实现(23)

立案後的费用产生 很多人会产生一个疑问,立案後如果没有营业跟有营业的费用产生的差别。 这里必须要先说...

Day09 Platform Channel - BasicMessageChannel

如同前面介绍的,Flutter 定义了三种不同型别的Platform Channel 在platfo...