V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
zhengchengdong
V2EX  ›  分享创造

做了一个 go 语言实现的简化应用开发的框架

  •  
  •   zhengchengdong · 2022-10-07 18:11:40 +08:00 · 1276 次点击
    这是一个创建于 565 天前的主题,其中的信息可能已经有所发展或是发生改变。

    https://github.com/zhengchengdong/ARP4G

    ARP4G

    ARP4G 是一个 go 语言实现的简化应用开发的框架。

    通过 Aggregate (聚合)、Repository (仓库)、Process (过程) 3 个概念,隔离业务逻辑和技术实现细节,使开发者专注于产品业务本身。

    一个简单的例子

    func (serv *OrderService) CompleteOrder(ctx context.Context, orderId string) *Order {
    	//从仓库取出 order
    	order, _ := serv.orderRepository.Take(ctx, orderId)
    	if order.state == "ongoing" {
    		//改变他的状态
    		order.state = "compleated"
    		//返回改变后的 order
    		return order
    	}
    	return nil
    }
    type OrderService struct {
    	orderRepository OrderRepository
    }
    type OrderRepository interface {
    	Take(ctx context.Context, id any) (*Order, bool)
    }
    

    这里我们首先从订单仓库取出了一个订单(聚合),随后改变了他的状态,变成“已完成”,最后返回了这个“已完成”的订单。

    这是一段业务代码,在这过程我们不关心查询和保存这些和数据库打交道的事情,我们也不关心 “并发改变订单状态所带来的问题” 这样的复杂技术细节,代码中没有任何技术细节,只有业务。

    我们需要做的仅仅是调用该业务方法时用 ARP4G 包装一下(无需侵入这段纯粹的业务代码),ARP4G 就会为你照顾一切技术细节。

    订单仓库 “orderRepository” 不需要开发,可以使用[ARP4G 的不同实现](#ARP4G 的不同实现)来实现

    安装

    1. 首先需要 Go 已安装(1.18 及以上版本), 然后可以用以下命令安装 ARP4G 。
    go get -u github.com/zhengchengdong/ARP4G
    
    1. 在你的代码中 import:
    import "github.com/zhengchengdong/ARP4G"
    

    快速开始

    package main
    import (
    	"context"
    	"fmt"
    	"github.com/zhengchengdong/ARP4G/arp"
    )
    func main() {
    	greetingService := &GreetingService{}
    	arp.Go(context.Background(), func(ctx context.Context) {
    		//调用业务方法
    		greetingService.SayHello(ctx)
    	})
    }
    type GreetingService struct {
    }
    func (serv *GreetingService) SayHello(ctx context.Context) {
    	fmt.Println("hello world")
    }
    

    ARP4G 的不同实现

    MongoDB

    ARP4G-mongodb

    Redis

    ARP4G-redis

    使用 ARP 开发业务简介

    假设有一段完成订单的业务,根据订单 id 找到相关订单,把它的状态改为已完成。以下将介绍如何使用Aggregate 、Repository 、Process3 个概念且利用 ARP4G 框架完成这段简单的业务。

    Aggregate (聚合)

    一个聚合就是表示某个实体的对象,该实体可能还包含别的实体,通常,这在业务上意味着被包含的实体是聚合的一部分,比如一辆汽车包含一个引擎。这里说的聚合就是DDD_Aggregate

    显然这里有一个聚合,Order

    type Order struct {
    	Id    string
    	State string
    }
    

    Repository (仓库)

    仓库,就是存放聚合对象的仓库。

    你可以把它想象成一个真实世界的仓库。例如有个品牌汽车专卖店,它有一个汽车仓库停满了汽车,当然它不会有引擎仓库,因为当你从仓库开出一辆汽车的时候就已经包含了它的引擎了。

    现在,我们有了订单仓库,OrderRepository

    type OrderRepository interface {
    	Take(ctx context.Context, id any) (order *Order, found bool)
    }
    

    仓库设计成接口是因为当我们在设计业务的时候,不希望扯入任何技术细节。在业务侧,我们需要的是有一个订单仓库,从中可以拿订单,并不关心仓库是怎么建造的。

    从仓库获取我要的聚合

    考虑一个现实世界中的汽车仓库,当一个顾客把一辆汽车买走后,另一个顾客是无法买走同一辆汽车的,同时顾客对自己的新车喷涂了喜欢的图案,当然另一个顾客无法喷涂这辆车,尽管他不喜欢这个图案,因为他没有取得它。我们把从仓库中取出聚合的操作叫做Take

    另一个场景,仓库管理员想要确认某辆车的型号是不是最新款,他来到仓库,找到了这辆车,确认了它的型号,记录在了自己的小本子上。他并不需要把这辆车从仓库开走,同时另一个仓库管理员也可以对同一辆车做确认。我们把这种只需要在仓库中找到聚合的操作叫做Find

    总结起来,当我们需要获取仓库中的聚合,并试图改变它的状态,那么用Take,如果我们只是想在仓库中找到一个聚合并想要看看它的状态,那么用Find

    这里我们要从仓库拿走这个订单,并改变它的状态,所以我们会有 Take

    Take(ctx context.Context, id any) (order *Order, found bool)
    

    完成我的业务逻辑

    我们还需要一个 OrderService

    type OrderService struct {
    	orderRepository OrderRepository
    }
    

    OrderService 提供一个业务方法来实现我们的业务逻辑

    func (serv *OrderService) CompleteOrder(ctx context.Context, orderId string) *Order {
    	//从仓库取出 order
    	order, _ := serv.orderRepository.Take(ctx, orderId)
    	if order.state == "ongoing" {
    		//改变他的状态
    		order.state = "compleated"
    		//返回改变后的 order
    		return order
    	}
    	return nil
    }
    

    看看里面做了些什么,从仓库取出 order ,然后改变它的状态。

    我们认为大多数业务都是这样,我们把从仓库中取出一个或几个聚合,并改变他们的状态这样的一个整体叫做Process(过程)。通常,一个过程就是一个业务服务的方法。

    最后我们需要用 ARP4G 框架来包装一下这个过程,从而照顾所有的技术细节

    func main() {
    	orderService := &OrderService{repoimpl.NewMemRepository(func() *Order { return &Order{} })}
    	arp.Go(context.Background(), func(ctx context.Context) {
    		//调用业务方法
    		orderService.CompleteOrder(ctx, "12345")
    	})
    }
    

    可以在下一节了解[ARP4G 做了什么](#ARP4G 做了什么)

    ARP4G 做了什么

    在这之前我愿介绍一些 ARP 的规则:

    1. 不能同时从仓库取得( Take )同一个聚合

    2. 聚合的状态更新是自然的,业务代码不需要显式的 update

      是的,考虑真实世界的仓库,当你买了一辆车,喷涂了新的图案,那么这辆车的外观就被改变了,这是自然的,这不需要你再告诉仓库要 update 才能确定下来。

    3. 一个过程作为一个整体,其中所有涉及到的聚合的状态改变要么全都成功要么一个都不成功

    4. 一个过程结束的时候,不管成功与否,确保从仓库取得( Take )的聚合全部归还仓库

    所以,ARP4G 所做的就是帮你实现了这些规则,使得你可以写出纯粹的业务代码。

    如何持久化到数据库总是关心的,关于持久化聚合到具体的数据库可以看[ARP4G 的不同实现](#ARP4G 的不同实现)。

    4 条回复    2022-10-10 17:47:56 +08:00
    kidlj
        1
    kidlj  
       2022-10-08 10:06:06 +08:00
    很不错,star 了,有机会学习下源码。
    zhengchengdong
        2
    zhengchengdong  
    OP
       2022-10-08 10:36:10 +08:00
    @kidlj 欢迎使用它来帮助到你的业务开发,关于源码有任何的问题和想法都请告诉我:)
    morty0
        3
    morty0  
       2022-10-10 16:16:05 +08:00
    玩具
    zhengchengdong
        4
    zhengchengdong  
    OP
       2022-10-10 17:47:56 +08:00
    @morty0 我们希望 ARP 像玩具一样简单:) 通过总结这些年我们上线的几十个业务系统,去除了很多不必要的复杂性,沉淀出了 ARP 设计方法,有任何疑问和想法欢迎讨论
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1629 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 16:57 · PVG 00:57 · LAX 09:57 · JFK 12:57
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.