领域模型-六边形架构 | DDD

共计 2990 个字符,预计需要花费 8 分钟才能阅读完成。

六边形架构与DDD

传统的分层架构

分层架构是一种通用的应用架构,它将应用分为多个独立的功能层,每个层都负责不同的功能,例如表现层、业务逻辑层、数据访问层等。这种架构的好处就是能够提高代码的可维护性、可扩展性,同时也可以降低系统的复杂性。

这种架构在实际开发过程中,会产生一些问题:

  1. 某些逻辑或某些数据处理该放在哪一层?

  2. 应该分多少层?

  3. 平层和跨层调用是否合理?

  4. 当程序耦合度逐渐增高时,很难或不可能允许该程序被另一个程序驱动

六边形架构

Alistair Cockburn提出了一种解决对称性问题中的应用架构。它可以看做是一种特殊的分层架构,区别在于它把多级的上下层变成了内外层。

在这种架构里,不同的客户通过平等的方式与系统进行交互。它强调应用通过端口与外部进行交互,而外部的实体也可以用同样的方式来处理。所以六边形架构又称“端口适配器架构”。

比如HTTP客户、MQ客户,甚至是测试用例。它们都是平等的对系统提供输入。RedisDB也是平等的提供输出。每个客户都有自己的适配器,去理解输入。这些输入和输出就是外部的适配器,而内部就是业务系统,开发者可以将重心放在业务逻辑上,隔离输入和输出。

DDD与六边形架构是如何结合的

领域模型-六边形架构 | DDD

所有的数据处理全部由repository适配成实体,逻辑都是领域服务、聚合和实体的行为。分多少层和平层、跨层调用都不存在。

项目结构

  • domain – 领域模型

    • aggregate – 聚合

    • dependency – 依赖倒置的接口

    • entity – 实体

    • dto – 传输对象

    • po – 持久化对象

    • *.go – 领域服务

  • adapter – 适配器

    • controller – 输入适配器

    • consumer – 输入适配器

    • repository – 输出适配器

  • infra – 基础设施

    • *.go – 基础设施组件

domain 领域模型

对应六边形的内部,主要放领域服务的代码。子目录分为aggregate聚合根目录,entity实体目录,dto或者vo是外部输入输出对象,po是数据库持久化对象。

adapter 适配器

对应六边形的外部,主要是输入和输出的适配器。controller子目录负责http的请求输入,repository子目录负责实体的读写。

外部adapter目录的controllerrepositoryconsumer依赖内部的domaindomain要使用repository处理po的读写,这样就产生了相互依赖。后续的依赖倒置会讲解如何解除这种依赖(外部依赖内部,内部依赖抽象)。

主要代码参考

// Package controller ..
package controller

import (
    ...
)

func init() {
    dt.Prepare(func(initiator dt.Initiator) {
        // 注册外部API
        if !initiator.IsPrivate() {
            initiator.BindController("/syncplans", &SyncPlan{})
        }
    })
}

// SyncPlan ..
type SyncPlan struct {
    Worker      dt.Worker       // 运行时对象 依赖注入请求运行时 无序侵入的传递
    Request     *requests.Request // 基础设施 用于处理客户端请求io的json数据和验证
    SyncPlanSrv *domain.SyncPlan  // 配置领域服务
    Roler       role.RoleHandler  // 角色管控组件
}

// Get GET:/syncplans/
func (c *SyncPlan) Get() dt.Result {
    page := hiveutils.StrToInt(c.Request.ReadQueryDefault("page", "1"))
    pageSize := hiveutils.StrToInt(c.Request.ReadQueryDefault("pagesize", "10"))
    voItems, totalPage, err := c.SyncPlanSrv.Items(page, pageSize)
    if err != nil {
        return &requests.JSONResponse{Error: err}
    }
    c.Worker.IrisContext().Header("X-Total-Page", strconv.Itoa(totalPage))
    return &requests.JSONResponse{Object: voItems}
}
// Package domain 领域服务
package domain

import (
    ...
)

func init() {
    // 绑定领域服务到框架,框架会根据客户的使用做依赖倒置和依赖注入的处理
    dt.Prepare(func(initiator dt.Initiator) {
        initiator.BindService(func() *SyncPlan {
            return &SyncPlan{}
        })
        // 控制器客户需要明确使用 InjectController
        initiator.InjectController(func(ctx hive.Context) (service *SyncPlan) {
            initiator.GetService(ctx, &service)
            return
        })
    })
}

// SyncPlan 同步计划领域服务
type SyncPlan struct {
    Worker       dt.Worker                   // 运行时对象
    SyncPlanRepo dependency.SyncPlanRepo       // 依赖倒置资源库 DIP Dependency Inversion Principle
    Cache        store.Cache                   // 缓存组件
    Transaction  *domainevent.EventTransaction // 依赖注入事务组件
}

// Items 查询同步计划列表
func (domain *SyncPlan) Items(page, pagesize int) (result []vo.SyncPlanItemRes, totalPage int, err error) {
    result = make([]vo.SyncPlanItemRes, 0)
    domain.Worker.Logger().Info("ready to get syncplan list...")
    entitys, totalPage, err := domain.SyncPlanRepo.Finds(page, pagesize)
    if err != nil {
        return
    }
    for i := 0; i < len(entitys); i++ {
        item := vo.SyncPlanItemRes{
            ID:               entitys[i].ID,
            SourceDoclibID:   entitys[i].SourceDoclibID,
            SourceDoclibType: entitys[i].SourceDoclibType,
            SourceDoclibName: entitys[i].SourceDoclibName,
            TargetDomainID:   entitys[i].TargetDomainID,
            TargetDoclibName: entitys[i].TargetDoclibName,
            Pattern:          entitys[i].Pattern,
            Status:           entitys[i].Status,
        }
        result = append(result, item)
    }
    return
}
正文完
 
Dustin
版权声明:本站原创文章,由 Dustin 2023-07-19发表,共计2990字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。