Posted in

Go微服务架构中设计模式选型难题,深度拆解6类场景下的最优解与反模式警示

第一章:Go微服务架构设计模式全景图谱

在现代云原生系统中,Go凭借其轻量协程、静态编译、高并发性能与简洁语法,成为构建微服务的首选语言。本章系统梳理适用于Go生态的核心架构设计模式,聚焦可落地性与工程实践适配度,而非抽象理论堆砌。

服务拆分策略

合理边界是微服务成败前提。推荐采用“业务能力域(Bounded Context)+ 数据所有权”双驱动原则:每个服务应独占其核心实体的CRUD权限,并通过领域事件(Domain Events)实现跨服务最终一致性。避免按技术层(如“所有用户接口归User-Service”)粗暴切分。

进程间通信模式

模式 Go典型实现 适用场景
同步HTTP/REST net/http + gin/chi 调试友好、强一致性要求
异步消息 github.com/segmentio/kafka-go 解耦、削峰、事件溯源
gRPC google.golang.org/grpc 内部高性能调用、多语言互通

服务发现与负载均衡

Go服务应主动向注册中心上报健康状态。以Consul为例,需集成hashicorp/consul/api并实现心跳续租:

// 初始化Consul客户端并注册服务
client, _ := consulapi.NewClient(consulapi.DefaultConfig())
reg := &consulapi.AgentServiceRegistration{
    ID:      "order-service-01",
    Name:    "order-service",
    Address: "10.0.1.100",
    Port:    8080,
    Check: &consulapi.AgentServiceCheck{
        HTTP:                           "http://10.0.1.100:8080/health",
        Timeout:                        "5s",
        Interval:                       "10s",
        DeregisterCriticalServiceAfter: "90s", // 健康检查失败90秒后自动注销
    },
}
client.Agent().ServiceRegister(reg)

容错与弹性设计

必须为所有外部依赖配置超时、重试与熔断。推荐使用sony/gobreaker实现熔断器,并结合golang.org/x/net/context控制请求生命周期:

// 熔断器配置示例
cb := gobreaker.NewCircuitBreaker(gobreaker.Settings{
    Name:        "payment-client",
    MaxRequests: 3,
    Timeout:     60 * time.Second,
    ReadyToTrip: func(counts gobreaker.Counts) bool {
        return counts.ConsecutiveFailures > 5 // 连续5次失败即熔断
    },
})

配置管理

禁止将配置硬编码或写入代码库。统一使用spf13/viper支持多源(环境变量、Consul KV、文件),优先级由高到低:命令行参数 > 环境变量 > 远程配置中心 > 配置文件。

第二章:服务发现与负载均衡场景的模式选型

2.1 基于Consul的客户端服务发现理论与go-micro实践

客户端服务发现模式中,服务消费者自行向注册中心(如Consul)查询可用实例,实现去中心化路由决策。

核心优势对比

特性 服务端发现 客户端发现
负载均衡位置 API网关/边车 应用进程内
一致性要求 强依赖网关状态 需本地缓存+健康检查
扩展性 网关成瓶颈 水平伸缩友好

go-micro集成示例

import "github.com/micro/go-micro/v4/registry/consul"

r := consul.NewRegistry(
    registry.Addrs("127.0.0.1:8500"), // Consul agent地址
    registry.Timeout(3*time.Second),   // 请求超时控制
)
service := micro.NewService(
    micro.Name("greeter"),
    micro.Registry(r),
)

该配置使go-micro在启动时自动向Consul注册,并在调用时通过Selector策略拉取健康节点列表。Addrs支持集群多节点,Timeout避免阻塞初始化流程。

数据同步机制

Consul采用gossip协议同步服务状态,客户端通过长轮询(?index=参数)实现低延迟变更感知,配合TTL健康检查保障实例列表实时性。

2.2 服务端负载均衡(API网关层)与gin+gorilla/mux路由策略落地

在API网关层实现服务端负载均衡,需结合路由分发与上游服务发现。gin 侧重高性能轻量路由,gorilla/mux 则提供更精细的路径匹配与变量约束能力。

路由策略对比

特性 gin gorilla/mux
路径参数提取 c.Param("id") mux.Vars(r)["id"]
正则路由约束 需中间件扩展 原生支持 r.HandleFunc("/user/{id:[0-9]+}", ...)
中间件链执行效率 更高(无反射) 略低(依赖反射解析)

gin 实现带权重的上游转发示例

// 基于Header灰度路由:X-Env=prod → prod-svc:8081;否则→ staging-svc:8082
r := gin.Default()
r.Any("/api/*path", func(c *gin.Context) {
    upstream := "http://prod-svc:8081"
    if c.GetHeader("X-Env") == "staging" {
        upstream = "http://staging-svc:8082"
    }
    proxy := httputil.NewSingleHostReverseProxy(&url.URL{Scheme: "http", Host: upstream})
    proxy.ServeHTTP(c.Writer, c.Request)
})

逻辑分析:该代码绕过传统负载均衡器,直接在网关层完成请求路由决策;X-Env 头作为灰度开关,参数可扩展为服务标签、用户ID哈希等策略源。

gorilla/mux 的路径语义化分发

// 按资源类型与版本分离路由树,提升可维护性
r := mux.NewRouter()
r.HandleFunc("/v1/users", userHandler).Methods("GET")
r.HandleFunc("/v2/users/{id:[0-9]+}", userV2Handler).Methods("GET")

此设计使版本演进与路由治理解耦,避免 gin 中大量 if version == "v2" 分支判断。

2.3 DNS轮询在K8s Service中的Go客户端适配与健康探测实现

Kubernetes Service 默认通过 kube-dns/CoreDNS 提供 DNS A 记录解析,返回所有就绪 Pod 的 IP 列表。Go 客户端需主动适配 DNS 轮询语义,并规避过期地址导致的连接失败。

健康探测驱动的 DNS 缓存刷新

使用 net.Resolver 配合 TTL 检查,结合后台 goroutine 定期探测各后端 HTTP /healthz 端点:

// 基于健康状态动态更新可用 endpoint 列表
func (c *DNSRoundRobin) refreshEndpoints() {
    ips, err := c.resolver.LookupHost(context.Background(), "my-service.default.svc.cluster.local")
    if err != nil { return }
    c.mu.Lock()
    defer c.mu.Unlock()
    c.endpoints = make([]string, 0)
    for _, ip := range ips {
        if isHealthy(ip + ":8080") { // 同步 HTTP 探活
            c.endpoints = append(c.endpoints, ip+":8080")
        }
    }
}

逻辑说明:LookupHost 获取全量 IP;isHealthy 发起带超时(300ms)的 HEAD 请求;仅保留响应码为 200 的实例。避免将 DNS TTL 与探测周期强耦合,采用独立 ticker 控制刷新频率(默认 10s)。

探测策略对比

策略 实时性 开销 适用场景
DNS TTL 缓存 低(依赖集群配置) 极低 静态服务
主动 HTTP 探活 高(秒级) 中(每实例单请求) 生产级有状态服务
TCP 连接预检 高(并发建连) 对延迟极度敏感场景

流程示意

graph TD
    A[启动定时器] --> B[DNS 解析 Service]
    B --> C[并发健康探测]
    C --> D{存活?}
    D -->|是| E[加入可用列表]
    D -->|否| F[丢弃该 IP]
    E --> G[RoundRobin 负载分发]

2.4 多集群服务发现中的跨区域路由决策模型与go-sdk封装

跨区域路由需在延迟、可用性与成本间动态权衡。核心决策模型基于加权多因子评分:score = w₁×RTT⁻¹ + w₂×SLA + w₃×Cost⁻¹,实时聚合各集群健康度、网络时延(ICMP+TCP探针)及云厂商区域定价。

路由策略配置示例

// 初始化跨区域路由客户端
client := multicluster.NewRouter(
    multicluster.WithRegionWeights(map[string]float64{
        "cn-shanghai": 0.7, // 高优先级
        "us-west-1":   0.3, // 容灾备用
    }),
    multicluster.WithFallbackTimeout(5 * time.Second),
)

WithRegionWeights设定区域基础权重;WithFallbackTimeout控制故障转移超时,避免雪崩。

决策因子来源对比

因子 数据源 更新频率 可信度
网络RTT 分布式Ping探针 5s ★★★★☆
服务可用率 Prometheus SLI指标 30s ★★★★
实例负载 cAdvisor + eBPF采集 10s ★★★☆

路由执行流程

graph TD
    A[请求入站] --> B{是否命中本地集群?}
    B -->|是| C[直连本地Endpoint]
    B -->|否| D[查询全局拓扑+实时指标]
    D --> E[应用加权评分模型]
    E --> F[选取Top1集群]
    F --> G[DNS重写或Sidecar转发]

2.5 反模式警示:硬编码Endpoint与静态服务列表导致的雪崩失效链

当服务发现机制缺失时,开发者常将下游地址写死在代码中:

// ❌ 危险示例:硬编码Endpoint
String paymentUrl = "https://payment-service-v1.internal:8080/process";
HttpResponse resp = httpClient.execute(new HttpGet(paymentUrl));

该调用完全绕过服务注册中心,一旦 payment-service-v1 实例宕机或扩容迁移,请求立即失败,且无法自动重试新节点。

雪崩传导路径

  • 单点故障 → 调用超时积压线程 → 连接池耗尽 → 上游服务拒绝新请求 → 故障沿调用链向上蔓延

对比方案演进

方案 动态性 容错能力 运维成本
硬编码Endpoint 无熔断/重试 高(每次变更需发版)
DNS轮询 ⚠️(TTL延迟) 有限
服务注册中心(如Nacos) 支持健康检查+自动摘除
graph TD
    A[客户端] -->|硬编码URL| B[payment-service-v1]
    B -->|宕机| C[连接超时]
    C --> D[线程阻塞]
    D --> E[上游HTTP队列满]
    E --> F[全站503]

第三章:分布式事务与状态一致性场景的模式选型

3.1 Saga模式在订单履约链路中的Go协程编排与补偿事务实现

Saga模式将长事务拆解为一系列本地事务,每个步骤均配备对应的补偿操作。在订单履约链路(创建订单→扣减库存→支付→发货)中,Go协程天然适配Saga的异步、非阻塞执行需求。

协程驱动的正向执行流程

func ExecuteOrderSaga(ctx context.Context, orderID string) error {
    // 启动各步骤协程,通过channel同步结果与错误
    resultCh := make(chan SagaStepResult, 4)
    go createOrder(ctx, orderID, resultCh)
    go reserveStock(ctx, orderID, resultCh)
    go processPayment(ctx, orderID, resultCh)
    go scheduleShipment(ctx, orderID, resultCh)

    // 收集4个步骤结果,任一失败即触发补偿链
    for i := 0; i < 4; i++ {
        res := <-resultCh
        if res.Err != nil {
            return compensateBackwards(res.StepIndex, orderID)
        }
    }
    return nil
}

逻辑分析:resultCh 容量为4,避免协程阻塞;compensateBackwards 依据失败步骤索引反向调用对应补偿函数(如 undoReserveStock)。ctx 保障超时与取消传播。

补偿事务关键约束

  • 补偿操作必须幂等且可重入
  • 补偿接口需独立于主事务数据库连接
  • 每个Saga步骤状态需持久化(如写入saga_log表)
步骤 正向操作 补偿操作 幂等键
1 INSERT INTO orders UPDATE orders SET status='canceled' order_id
2 UPDATE inventory SET locked=1 UPDATE inventory SET locked=0 sku_id + order_id

状态流转示意

graph TD
    A[Start] --> B[createOrder]
    B --> C[reserveStock]
    C --> D[processPayment]
    D --> E[scheduleShipment]
    B -.-> F[undoCreateOrder]
    C -.-> G[undoReserveStock]
    D -.-> H[undoProcessPayment]
    E -.-> I[undoScheduleShipment]

3.2 TCC模式在库存扣减场景下的Go接口契约设计与幂等性保障

核心接口契约定义

TCC三阶段需严格分离 Try/Confirm/Cancel 职责,契约须携带业务唯一ID与幂等令牌:

type InventoryTCCService interface {
    Try(ctx context.Context, req *TryDeductReq) error
    Confirm(ctx context.Context, req *ConfirmReq) error
    Cancel(ctx context.Context, req *CancelReq) error
}

type TryDeductReq struct {
    OrderID     string `json:"order_id"`     // 业务主键,全局唯一
    SkuID       string `json:"sku_id"`
    Quantity    int    `json:"quantity"`
    Idempotency string `json:"idempotency"`  // JWT签名令牌,含时间戳+随机数+业务ID
}

逻辑分析Idempotency 字段由调用方生成(如 HMAC-SHA256(order_id + timestamp + nonce)),服务端校验时效性(≤5min)与签名有效性,避免重复Try导致超扣。OrderID 作为事务上下文锚点,支撑Confirm/Cancel精准定位。

幂等状态机管理

使用Redis原子操作维护状态跃迁,仅允许 INIT → TRYING → CONFIRMED/CANCELLED

当前状态 允许跃迁至 约束条件
INIT TRYING Idempotency未存在
TRYING CONFIRMED Confirm请求且库存充足
TRYING CANCELLED Cancel请求或超时自动回滚

数据同步机制

Confirm失败时触发最终一致性补偿:

graph TD
    A[Try成功] --> B[写入冻结库存+幂等记录]
    B --> C{Confirm调用}
    C -->|成功| D[扣减可用库存]
    C -->|失败| E[异步重试+告警]
    E --> F[超3次→人工介入]

3.3 反模式警示:本地事务误用与“伪两阶段提交”引发的数据不一致

数据同步机制的常见陷阱

开发者常在微服务中用「本地事务 + 消息表 + 定时轮询」模拟分布式事务,实则构成危险的“伪两阶段提交”。

典型错误代码示例

@Transactional // 仅保证DB操作原子性
public void transfer(String from, String to, BigDecimal amount) {
    accountMapper.debit(from, amount);           // ✅ DB本地事务内
    messageMapper.insert("transfer_event", ...); // ✅ 同库消息表写入
    kafkaTemplate.send("transfer-topic", ...);   // ❌ 跨网络调用——无事务保障!
}

逻辑分析@Transactional 仅覆盖 debit()insert()(因共享同一数据源),但 kafkaTemplate.send() 是异步网络I/O,失败将导致数据库已扣款而下游未收到事件,产生永久性不一致。参数 amount 无法回滚至 Kafka 层。

伪2PC失败路径(mermaid)

graph TD
    A[开始本地事务] --> B[更新账户余额]
    B --> C[写入消息表]
    C --> D[发送Kafka消息]
    D -- 网络超时/ broker宕机 --> E[消息丢失]
    E --> F[DB已提交,状态不可逆]

正确解法对比

方案 是否强一致 是否需额外组件 风险点
本地事务+Kafka直发 消息发送无事务锚点
Saga模式 ✅(最终) 补偿逻辑复杂
Seata AT 模式 ✅(全局) 依赖代理与undo_log表

第四章:弹性容错与可观测性增强场景的模式选型

4.1 断路器模式在gRPC调用链中的go-hystrix替代方案与gobreaker实战

go-hystrix 已归档,社区主流转向轻量、无依赖的 gobreaker。其基于状态机(Closed → Open → Half-Open)实现,内存开销低且天然兼容 gRPC 的拦截器模型。

集成 gRPC Unary 拦截器

func breakerUnaryClientInterceptor(cb *gobreaker.CircuitBreaker) grpc.UnaryClientInterceptor {
    return func(ctx context.Context, method string, req, reply interface{},
        cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
        _, err := cb.Execute(func() (interface{}, error) {
            return nil, invoker(ctx, method, req, reply, cc, opts...)
        })
        return err
    }
}

逻辑分析:cb.Execute 封装原始调用,自动统计失败/超时;gobreaker.SettingsName 用于指标区分,Timeout 应略大于 gRPC context.WithTimeoutReadyToTrip 可自定义熔断判定(如连续5次失败)。

状态迁移对比

状态 触发条件 行为
Closed 错误率 正常转发
Open 达到失败阈值 直接返回 ErrTooManyRequests
Half-Open Open 状态持续 SleepWindow 允许单个试探请求
graph TD
    A[Closed] -->|失败率超阈值| B[Open]
    B -->|SleepWindow到期| C[Half-Open]
    C -->|试探成功| A
    C -->|试探失败| B

4.2 重试与退避策略在HTTP Client层的context超时整合与指数退避封装

HTTP客户端需协同管理请求超时、重试逻辑与退避节奏,避免雪崩与资源耗尽。

context超时与重试生命周期绑定

Go中context.WithTimeout生成的ctx应贯穿整个重试循环——每次http.NewRequestWithContext(ctx)均继承同一截止时间,而非为每次重试新建独立超时。

// 指数退避封装:基于context Deadline动态裁剪最大重试次数
func NewExponentialBackoffClient(base *http.Client, maxRetries int) *BackoffClient {
    return &BackoffClient{
        client:     base,
        maxRetries: maxRetries,
        backoff:    time.Millisecond * 100, // 初始间隔
    }
}

func (b *BackoffClient) Do(req *http.Request) (*http.Response, error) {
    var err error
    for i := 0; i <= b.maxRetries; i++ {
        resp, err := b.client.Do(req)
        if err == nil {
            return resp, nil
        }
        if i == b.maxRetries || !isRetryable(err) {
            return nil, err
        }
        select {
        case <-time.After(b.backoff):
        case <-req.Context().Done(): // 上层context已超时,立即退出
            return nil, req.Context().Err()
        }
        b.backoff = min(b.backoff*2, time.Second) // 指数增长,上限1s
    }
    return nil, err
}

逻辑分析req.Context()来自调用方(如context.WithTimeout(parent, 5*time.Second)),每次time.After前校验ctx.Done(),确保不因退避而突破总时限;backoff从100ms起翻倍,但 capped at 1s,防止长尾延迟。

退避参数对照表

参数 推荐值 说明
初始间隔 100–250ms 平衡首试响应与容错性
乘数 2 标准指数退避
上限 1–5s 防止单次重试过久阻塞

重试决策流程

graph TD
    A[发起请求] --> B{响应成功?}
    B -->|是| C[返回Response]
    B -->|否| D{是否可重试?<br>(网络错误/5xx)}
    D -->|否| E[返回错误]
    D -->|是| F{已达maxRetries?}
    F -->|是| E
    F -->|否| G[等待退避间隔]
    G --> A

4.3 分布式追踪中OpenTelemetry SDK在Go微服务中的Span注入与上下文透传

Span注入:从HTTP请求中提取并续接追踪上下文

OpenTelemetry Go SDK通过otelhttp.NewHandler自动从traceparent HTTP头提取SpanContext,并在新Span中设置parent关系:

import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"

mux := http.NewServeMux()
mux.Handle("/api/users", otelhttp.NewHandler(
    http.HandlerFunc(getUsers),
    "GET /api/users",
    otelhttp.WithSpanNameFormatter(func(_ string, r *http.Request) string {
        return fmt.Sprintf("HTTP %s %s", r.Method, r.URL.Path)
    }),
))

otelhttp.NewHandler封装了propagation.HTTPTraceContext解析逻辑,自动调用tracer.Start(ctx, name, trace.WithSpanKind(trace.SpanKindServer)),其中ctx已由propagation.Extractr.Header恢复。WithSpanNameFormatter支持动态命名,提升可读性。

上下文透传:跨goroutine与异步调用的延续

使用context.WithValue无法跨goroutine安全传递Span——必须显式携带context.Context

func getUsers(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context() // 已含parent Span
    users, err := fetchFromDB(ctx) // 透传ctx至下游
    if err != nil { /* ... */ }
}

fetchFromDB需接收ctx context.Context并调用tracer.Start(ctx, "db.query"),确保子Span正确链入调用链。遗漏ctx传递将导致断链(orphaned span)。

关键传播机制对比

机制 适用场景 是否自动 风险点
HTTPTraceContext HTTP/gRPC跨服务 是(需中间件) 头缺失或格式错误导致丢失
context.Context 同进程内goroutine/DB调用 否(需手动传递) 忘记传参导致上下文断裂
otel.GetTextMapPropagator() 自定义协议(如MQ消息头) 需手动Inject/Extract 序列化/反序列化不一致
graph TD
    A[Client Request] -->|traceparent header| B[API Gateway]
    B -->|ctx with Span| C[UserService]
    C -->|ctx passed explicitly| D[DB Client]
    D -->|no context| E[Orphaned Span]
    C -->|ctx passed| F[Cache Client]

4.4 反模式警示:无熔断日志、无指标埋点的“盲区式”容错配置

当 Hystrix 或 Resilience4j 配置仅启用基础熔断逻辑却忽略可观测性基建,系统便陷入“心跳可知、病因未知”的运维盲区。

熔断器静默失效的典型配置

// ❌ 危险:无状态记录、无指标上报
CircuitBreakerConfig customConfig = CircuitBreakerConfig.custom()
    .failureRateThreshold(50)
    .waitDurationInOpenState(Duration.ofSeconds(60))
    .ringBufferSizeInHalfOpenState(10)
    .build();

该配置未集成 CircuitBreakerRegistry 的事件监听器,也未注册 Micrometer 指标绑定,导致熔断触发时既无日志痕迹,也无 resilience4j.circuitbreaker.calls 等关键指标暴露。

观测能力缺失的后果对比

维度 有日志+指标 无日志+指标(盲区式)
故障定位时效 秒级关联调用链与熔断事件 需人工翻查全量日志+重启复现
容错策略调优 基于失败率/响应延迟热力图迭代 凭经验硬编码阈值,易过度保守

正确演进路径

  • ✅ 注册 CircuitBreakerEventConsumer 实现日志透出
  • ✅ 通过 TaggedCircuitBreakerMetrics 对接 Prometheus
  • ✅ 在 Open 状态下自动触发 health check trace 上报
graph TD
    A[请求进入] --> B{熔断器判断}
    B -->|CLOSED| C[执行业务]
    B -->|OPEN| D[直接返回fallback]
    C -->|失败| E[记录FailureEvent]
    D --> F[上报OpenEvent+计数器]
    E & F --> G[(Prometheus + Loki)]

第五章:演进式架构下的模式治理与技术债防控

模式即契约:在微服务间强制实施API契约验证

某金融中台团队将OpenAPI 3.0规范嵌入CI流水线,在每次PR提交时自动调用spectral执行规则检查(如operation-id-uniqueno-$ref-siblings),并结合prism mock启动契约模拟服务供前端联调。当新增的/v2/transfer端点未声明x-biz-scenario: cross-border扩展字段时,流水线直接阻断合并。该机制上线后,跨域调用失败率下降76%,契约漂移引发的生产事故归零。

技术债仪表盘:基于SonarQube+自定义规则的量化追踪

团队构建了动态债务看板,集成以下维度数据:

指标类型 规则示例 阈值触发动作
架构腐化 package-cycle检测循环依赖 >2个包环路 → 自动创建Jira任务
安全漏洞 java:S2068(硬编码密码) 严重级漏洞 → 阻断发布
维护性衰减 cognitive-complexity > 15 单方法复杂度超限 → 强制关联重构卡

所有数据通过SonarQube Web API每日同步至Grafana,债务密度(Technical Debt Ratio)趋势图如下:

graph LR
    A[2023-Q3: 12.4%] --> B[2024-Q1: 8.7%]
    B --> C[2024-Q2: 5.2%]
    C --> D[2024-Q3: 3.9%]
    style D fill:#28a745,stroke:#28a745

演进式重构:用特性开关驱动渐进式模式迁移

支付网关从Dubbo迁移到gRPC时,采用LaunchDarkly实现双协议并行:所有新服务注册gRPC endpoint,旧客户端通过开关grpc_fallback_enabled决定是否降级调用Dubbo。监控显示gRPC调用量周环比增长18%,当成功率稳定≥99.95%且延迟P95

治理沙盒:在预发环境模拟架构变更影响

使用Chaos Mesh在K8s预发集群注入网络分区故障,验证服务网格中CircuitBreaker策略对熔断阈值的适应性。当将maxRequests=100调整为maxRequests=50后,通过Prometheus查询istio_requests_total{destination_service=~"auth.*"}指标,确认错误率未突破SLA(0.5%)。该沙盒每月执行3次架构变更压力测试,拦截了2起潜在雪崩风险。

债务偿还SLO:将技术债修复纳入研发效能考核

制定《技术债偿还承诺书》,要求每个迭代必须完成至少1项债务消除任务:例如将LegacyOrderService中硬编码的费率计算逻辑抽离为独立RateEngine微服务,并通过contract-test验证其与上下游的兼容性。QA团队使用Pact Broker验证契约版本兼容矩阵,确保v1.2消费者可安全升级至v1.3提供者。

架构决策记录:用ADR模板固化关键演进依据

针对“是否引入Event Sourcing替代CRUD”争议,团队创建ADR-042,明确记载:

  • 决策:否决全量事件溯源,仅在账户余额场景采用
  • 依据:审计合规要求(需完整资金流向)+ 现有MySQL Binlog已满足90%场景
  • 迁移路径:先改造AccountAggregate为事件驱动,再逐步替换TransactionRepository

该ADR存于Git仓库根目录,所有后续代码审查必须引用对应编号。

热爱 Go 语言的简洁与高效,持续学习,乐于分享。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注