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

golang 依赖循环的问题。

  •  
  •   awanganddong · 2024-08-09 17:31:51 +08:00 · 5999 次点击
    这是一个创建于 393 天前的主题,其中的信息可能已经有所发展或是发生改变。

    service 层会调用 tasks 任务

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

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

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

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

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

    ---

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

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

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

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

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

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

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

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

    这风格有点像写 c 驱动程序,你试试吧,不知道行不行😊
    cheng6563
        30
    cheng6563  
       2024-08-12 09:15:13 +08:00   ❤️ 1
    业务层循环依赖是很正常的需求,不支持循环依赖才是问题.
    awanganddong
        31
    awanganddong  
    OP
       2024-08-12 09:45:35 +08:00
    我有一些悟了。
    这是我依托 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
       2024-08-12 10:33:00 +08:00
    我定义 service 主要是处理业务逻辑。tasks 主要是队列相关,用的包是 asynq 。比如服务端一些定时器。我是在 tasks 触发,然后调用这个任务,然后这个任务执行完成之后,在十秒之后会再次执行。这时候就需要在 task 内调用这个任务。
    如果这两个公用一个调用方法,就会依赖循环。
    xiaozhang1997
        33
    xiaozhang1997  
       2024-08-12 12:10:33 +08:00
    别尬黑哦 java 不存在 go 的这种依赖问题 而且有些人是转语言,人家问一个比较成熟的优解,上面一群人修啥优越感呢
    qq978746873
        34
    qq978746873  
       2024-08-12 14:17:14 +08:00
    看看能不能用回调实现
    vczyh
        35
    vczyh  
       2024-08-12 14:48:21 +08:00
    感觉没说清楚,task 调用 service ,然后 service 又调用 task 了?
    securityCoding
        36
    securityCoding  
       2024-08-15 09:33:57 +08:00 via Android
    抽独立组件出来就好,没那么多讲究
    8520ccc
        37
    8520ccc  
       2024-08-20 04:37:48 +08:00 via iPhone
    拿一个目录来专门做接口

    最好用上代码自动生成

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