V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
awanganddong
V2EX  ›  Go 编程语言

golang 依赖循环的问题。

  •  
  •   awanganddong · 135 天前 · 4509 次点击
    这是一个创建于 135 天前的主题,其中的信息可能已经有所发展或是发生改变。

    service 层会调用 tasks 任务

    tasks 任务里边也会循环调用自身。

    然后就依赖循环了,对于这种,大家怎么解决。

    现在最 low 的办法,就是直接写两份代码。

    37 条回复    2024-08-20 04:37:48 +08:00
    zihuyishi
        1
    zihuyishi  
       135 天前
    抽出接口呗,这不是 jawa 常用手段,所以还是要多写写 jawa 呀
    czyt
        2
    czyt  
       135 天前
    拆分
    body007
        3
    body007  
       135 天前   ❤️ 1
    A 依赖 B 的代码,和 B 依赖 A 的代码,提取出来放到 C 包里面,这样 A 依赖 C ,B 依赖 C 就行了。
    FanGanXS
        4
    FanGanXS  
       135 天前
    中间多加一层,让 service 依赖于这一层,task 也依赖这一层。
    lt0136
        5
    lt0136  
       135 天前 via Android
    最简单的办法:service 和 task 写在一个 package 里
    fishofcat
        6
    fishofcat  
       135 天前
    抽象出来到别的包啊
    maxwellz
        7
    maxwellz  
       135 天前
    哈哈哈,之前也遇到过,最简单的方法就是单独建一个包,让 A 依赖 C ,B 依赖 C ,要么就是通过接口来解耦
    其实归结起来还是调用职责没划分好,要避免同级 package 互相调用
    awanganddong
        8
    awanganddong  
    OP
       135 天前
    大家说的我理解了,但是我不知道怎么入手,有 demo 吗
    yb2313
        9
    yb2313  
       135 天前   ❤️ 1
    遇到这种我一般直接打屁股😡
    povsister
        10
    povsister  
       135 天前
    遇到这种层次设计基本功有问题的,只能说
    菜,就多练.jpg
    younger027
        11
    younger027  
       135 天前   ❤️ 19
    人家问问题,你会就解答,不会就闭嘴。最烦 10 楼的,讲了一堆 pi 话,来显示自己来了?
    yanyao233
        12
    yanyao233  
       135 天前 via Android   ❤️ 1
    @zihuyishi 你这个爪哇怎么还带口音的
    Immortal
        13
    Immortal  
       135 天前
    把公共部分抽出来,两个分别各自引用
    weiwenhao
        14
    weiwenhao  
       134 天前
    把循环调用自身的逻辑抽离出来,放在单独的 service 或者其他 tasks 里面。
    yplam
        15
    yplam  
       134 天前 via Android   ❤️ 1
    参考依赖注入的模式,interface 抽出来,service 只依赖 interface ,然后在 main 或者写个容器进行服务初始化操作
    yrj
        16
    yrj  
       134 天前
    设计的问题。task 的任务应该归 task 所有。不要拆到公共 service 里
    Curtion
        18
    Curtion  
       134 天前
    加中间层呀,还能怎么办
    changz
        19
    changz  
       134 天前 via Android
    爪蛙还是写得太少了 /狗头
    gam2046
        20
    gam2046  
       134 天前
    @awanganddong #8

    ModuleA 需要调用 ModuleB.foo
    ModuleB 需要调用 ModuleA.foo

    ---

    现在创建一个 ModuleC ,把原本 A/B 内的方法移过来,fooA/fooB

    ModuleA 调用 ModuleC.fooB
    ModuleB 调用 ModuleC.fooA
    codebigbang
        21
    codebigbang  
       134 天前
    「没有什么是加个中间层不能解决的,如果有,就再加一层」-by 小白 debug
    james122333
        22
    james122333  
       134 天前 via Android
    service 为何需要调用任务包的东西? 都是写在 service 层不是吗 非即时性的在 service 层写个队列在 service 层塞入供任务层取用即可 即时性的本身就该放在 service 层或者更底层 表层呼叫
    不知道你在做什么
    james122333
        23
    james122333  
       134 天前 via Android
    象牙多层球(鬼工球)知道吧?
    Shoukaku
        24
    Shoukaku  
       134 天前
    人人都恨设计模式,人人都用设计模式😂
    Yoruno
        25
    Yoruno  
       134 天前 via Android
    可以把 task 或 service 对外使用 interface
    lolizeppelin
        26
    lolizeppelin  
       134 天前
    前面都说得不够具体....

    task 设计错误或者说抽象不足,没想清楚 task 到底要负责什么,边界是什么,想清楚就好办了
    通常的 task 要么在排队,要么执行,而不是和服务的概念混在一起

    简化的 task 设计
    执行返回的对象是下一个 task,就可以不停执行了

    如果你的任务还需要条件,那么把 task 设计成状态机或工作流
    简单的 task 执行返回增加一些状态之类用于工作流流控制、延迟值用于延迟灯

    这样你的服务就和 task 剥离了,如何被 task 调用或者调用 task 就简单了

    你会 python 的话参考一下 openstack 的 taskflow 的设计就知道如何设计 task 了
    w568w
        27
    w568w  
       134 天前
    这个本质上不是 Java 的问题,你换哪个语言都有这样的问题,Rust 、Dart 等新兴语言,你这么写也是报错,也得拆。

    根本上,就像 #26 说的,是设计上的错误:楼主没有搞懂自己想要什么架构,只是随意地把模块放在名字相近的包里,然后需要哪个模块就去直接导入那整个包…… 然后就出问题了。
    wwhontheway
        28
    wwhontheway  
       134 天前
    边界的问题,service 应该是提供服务的主体,不应该调用 tasks
    bugfan
        29
    bugfan  
       133 天前
    楼上说的都是抽取出公共部分单独放一个包或者文件夹里。如果 service 调用 tasks 的代码函数不多,启动时候在 tasks 里把 service 需要调用的逻辑函数 Register 到 service 里面,让 service 调那个注册进去的东西。这样就不用写两份代码了~~

    这风格有点像写 c 驱动程序,你试试吧,不知道行不行😊
    cheng6563
        30
    cheng6563  
       132 天前   ❤️ 1
    业务层循环依赖是很正常的需求,不支持循环依赖才是问题.
    awanganddong
        31
    awanganddong  
    OP
       132 天前
    我有一些悟了。
    这是我依托 chatgpt 生成的目录结构。
    这样就可以实现,从 service 和 task 内部对任务的调用。
    下一个环节就是对调用的抽离,支持所有的 task 。( HandleTask )主要是这个方法。

    package main

    import (
    "context"
    "fmt"
    "log"
    "time"

    "github.com/hibiken/asynq"
    )

    // 定义一个任务类型
    const TaskType = "task:example"

    // 定义一个通用的 TaskEnqueuer 结构体
    type TaskEnqueuer struct {
    Client *asynq.Client
    }

    // 公共的 EnqueueTask 方法
    func (te *TaskEnqueuer) EnqueueTask(taskType string, payload interface{}, delay time.Duration) error {
    task := asynq.NewTask(taskType, asynq.PayloadFrom(payload))
    _, err := te.Client.Enqueue(task, asynq.ProcessIn(delay))
    return err
    }

    // TaskHandler 结构体,现在包含一个 TaskEnqueuer
    type TaskHandler struct {
    Enqueuer *TaskEnqueuer
    }

    // HandleTask 方法,用于处理任务
    func (h *TaskHandler) HandleTask(ctx context.Context, task *asynq.Task) error {
    var depth int
    err := task.Payload().Unmarshal(&depth)
    if err != nil {
    return err
    }

    fmt.Printf("Executing task, Depth: %d\n", depth)

    if depth > 0 {
    // 调用公共的 EnqueueTask 方法,递归调用自身
    return h.Enqueuer.EnqueueTask(TaskType, depth-1, 1*time.Second)
    }

    return nil
    }

    // NewTaskHandler 工厂函数,用于初始化 TaskHandler 和 TaskEnqueuer
    func NewTaskHandler(redisAddr string) (*TaskHandler, *asynq.Server) {
    r := asynq.RedisClientOpt{Addr: redisAddr}

    client := asynq.NewClient(r)
    enqueuer := &TaskEnqueuer{Client: client}
    server := asynq.NewServer(r, asynq.Config{
    Concurrency: 10,
    })

    return &TaskHandler{Enqueuer: enqueuer}, server
    }

    // SetupAndRunServer 函数用于设置和启动服务器
    func SetupAndRunServer(server *asynq.Server, handler *TaskHandler) {
    mux := asynq.NewServeMux()
    mux.Handle(TaskType, asynq.HandlerFunc(handler.HandleTask))

    if err := server.Run(mux); err != nil {
    log.Fatalf("could not run server: %v", err)
    }
    }

    // main 函数作为程序入口
    func main() {
    redisAddr := "127.0.0.1:6379"
    handler, server := NewTaskHandler(redisAddr)
    defer handler.Enqueuer.Client.Close()

    // 初始化任务并加入队列
    err := handler.Enqueuer.EnqueueTask(TaskType, 3, 0) // 递归深度为 3 ,立即执行
    if err != nil {
    log.Fatalf("could not enqueue task: %v", err)
    }

    // 启动服务器处理任务
    SetupAndRunServer(server, handler)
    }
    awanganddong
        32
    awanganddong  
    OP
       132 天前
    我定义 service 主要是处理业务逻辑。tasks 主要是队列相关,用的包是 asynq 。比如服务端一些定时器。我是在 tasks 触发,然后调用这个任务,然后这个任务执行完成之后,在十秒之后会再次执行。这时候就需要在 task 内调用这个任务。
    如果这两个公用一个调用方法,就会依赖循环。
    xiaozhang1997
        33
    xiaozhang1997  
       132 天前
    别尬黑哦 java 不存在 go 的这种依赖问题 而且有些人是转语言,人家问一个比较成熟的优解,上面一群人修啥优越感呢
    qq978746873
        34
    qq978746873  
       132 天前
    看看能不能用回调实现
    vczyh
        35
    vczyh  
       132 天前
    感觉没说清楚,task 调用 service ,然后 service 又调用 task 了?
    securityCoding
        36
    securityCoding  
       129 天前 via Android
    抽独立组件出来就好,没那么多讲究
    8520ccc
        37
    8520ccc  
       124 天前 via iPhone
    拿一个目录来专门做接口

    最好用上代码自动生成

    写完代码自动生成接口,自动注册
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2780 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 11:39 · PVG 19:39 · LAX 03:39 · JFK 06:39
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.