【问题标题】:How to rethink the architecture of my Python project containing multiple async interfaces如何重新思考包含多个异步接口的 Python 项目的架构
【发布时间】:2022-08-21 22:11:22
【问题描述】:

我正在开发一个 Twitch Bot 大约一年。随着时间的推移,机器人变得越来越大以添加功能。现在,该机器人可以管理多个接口,包括 Discord、Twitch、Twitch API、Streamlabs ......并拥有一个 Web 界面来接收来自 OAuth 身份验证 API 的所有回调,并将 HTML 页面上的一些统计信息呈现给流媒体。

然而,机器人越大,我遇到的问题就越多。事实是,我不认为我当前的架构是好的。这就是它目前的做法:

首先,我实例化一个Core 类,该类将包含所有机器人和接口的实例,并存储它们之间共享的变量。它还包含数据库的全局异步锁(由于整个机器人在单个异步循环上运行,我需要确保只有一个接口同时与数据库通信)。

class Core:
    def __init__(self):
        # Define session lock for database access
        self.session_lock: asyncio.locks.Lock = asyncio.Lock()

        # Store bots & API access
        self.discord_bot: Optional[discord.Client] = None
        self.twitch_bot: Optional[twitchio.ext.commands.Bot] = None  # Manage Twitch IRC chat
        self.twitch_api: TwitchApi = None  # Manage Twitch API
        # Other interfaces ...

        # Some shared attributes which are read by all interfaces ...

然后,我通过传递核心来实例化我的所有接口。每个接口在实例化时都会自行注册到内核中。这是接口初始化的示例(此处为 Discord):

class DiscordBot(commands.Bot):
    def __init__(self, core: Core, **options):
        super().__init__(**options)

        self.core: Core = core
        self.core.discord_bot = self

我的主脚本中的实例化阶段:

core = Core()

discord_bot = DiscordBot(core)
twitch_bot = TwitchChatBot(core, os.environ[\'TWITCH_BOT_TMI_TOKEN\'], [os.environ[\'TWITCH_CHANNEL_NAME\']])

loop = asyncio.get_event_loop()
loop.create_task(twitch_bot.connect())
loop.create_task(discord_bot.start(os.environ[\"DISCORD_BOT_TOKEN\"]))
loop.run_forever()

这是我的架构的全局图及其管理方式:

这种架构非常方便,因为它允许我在接口之间建立一个非常简单的桥梁。例如,如果我想通过我的 Twitch 机器人在 Discord 上发布消息,我只需拨打 self.core.discord_bot.get_channel(...).send()。另一个方向也一样。

但我觉得这种架构不再可持续。目前Core 类包含超过5000 行代码和60 多个在所有接口之间共享的方法。我想在多个文件中分解它,但它是一团糟。此外,我越来越认为从长远来看,在同一个异步循环上运行所有接口并不是一个好主意。

我想到了解决方案,比如将所有接口分成不同的进程。但是,如何在不做复杂事情的情况下管理这些进程之间的同步(我以 Twitch bot 在 Discord 上发布消息为例)。我还查看了 Redis 之类的同步解决方案,但我真的不知道它是否可以回答我所有的问题......我还考虑过使用 python 导入模块直接导入 Core 实例(因此不必注册每个界面中的核心),但我不确定它是否会起作用以及它是否是一个好习惯。

我再举一个例子,在Core 类中实例化了一个处理机器人和社区之间交互的类。它用于改变与用户的交互(一种原始聊天机器人)。这个类必须在我的所有界面之间共享,因为我希望 Discord 机器人的反应根据 Twitch 上发生的事情做出反应。

无论如何,我想听听您对此的专家意见。你会如何组织这一切? 谢谢 :)

    标签: python design-patterns architecture bots


    【解决方案1】:

    您的描述中有一些不清楚的地方,我认为这可能很重要:

    图表底部的机器人是连接外部服务,然后向核心提供 API 接口,还是它们驾驶做核心工作?

    用企业说话:业务逻辑在哪里?

    我想我一直在建立一个类似的系统。

    在我的:

    • 有一小部分服务:(我不确定这些是否类似于您的机器人。)

      • Twitch 聊天界面。
      • Twitch 兑换界面。
      • KoFi 的接口(这样我就可以实时对捐赠做出反应)
      • 可以说日志系统可以被认为是一个。
      • 我没有数据库,但如果我有,服务会放在这里。
      • 我不使用 YouTube,但如果我使用了,该服务会转到此处。
      • 我考虑将我的 StreamLabs 桌面 API 接口放在这里 [通过 PySLOBS],但我还没有打扰。
      • 我考虑过在这里放置一个通用的异步循环,但我还没有打扰。
    • 有框架:

      • 它实例化并拥有幸存者。
      • 它注册、启动和停止插件(如下)。
    • 有一组更大的插件。

      • 每个插件只做一件事。
        • 例如如果有人兑换了 TextToSpeech 奖励,它会播放语音。该插件通过服务订阅兑换,并在触发时执行。
        • 例如如果有人在聊天中输入“GG”,它会在直播中播放动画。该插件通过服务订阅聊天,并在触发时连接到 StreamLabs。
      • 有些插件非常被动;他们在被主应用程序回调时采取行动。
      • 有些插件有单独的线程,所以它们可以独立运行。
      • 一些插件的线程会产生异步循环(尤其是因为 PySLOBS 使用异步)。
      • 一些插件有单独的进程(尤其是它们可以生成 Kivy 或 PyGame 窗口)。

    在我看来,您已经将我放入单独、独立、松散耦合的插件中的所有代码都放在了核心中的一个位置,并且变得笨拙。

    考虑将其分解为具有相同界面(启动、停止以及可能用于监视的某些状态)但只处理一项功能的各个部分。大多数人可能只会与一两个服务交谈,并且非常简单。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-07-01
      • 2016-10-03
      • 1970-01-01
      • 2011-09-12
      • 1970-01-01
      • 2011-10-26
      • 1970-01-01
      • 2022-08-16
      相关资源
      最近更新 更多