这是我第一次尝试将干净的架构原则应用于工作项目,但我遇到了许多问题。任务的本质:
有外部服务和 CRM。客户可能来自外部服务,需要转移到 CRM。如果客户已存在于 CRM 中,则需要在外部服务中检查其上次修改时间。如果更改时间与数据库中的数据不同,您需要使用客户端 ID 更新 CRM 中的信息。
我的实施问题:
对 CRM、数据库和其他外部系统运行查询的正确位置在哪里?例如,我有一个名为 ACrmClientsRepository(A 是 CRM 的常规名称)的 CRM 存储库的实现,如下所示:
class ACrmClientsRepository implements ClientsRepositoryInterface
{
public function __construct(private readonly ClientsDBInterface $clientsDB)
{
}
public function create(CreateClientDTO $createClientDTO)
{
$createdRecord = ACrmApi::getInstance()->api()->records()->createRecord('Clients', [
// ДАННЫЕ
]);
$this->clientsDB->create([
// ДАННЫЕ НА СОЗДАНИЕ В БД
]);
// ПЕРЕДАЮ ДАННЫЕ ДАЛЕЕ
}
}
我为使用外部服务制定了类似的结构。这种方法如何符合干净架构的原则?是否可以在实施中直接查询CRM和数据库?
- 我将与数据库的交互转移到单独的实现中,并将它们捆绑到 DI 容器中。例子:
public function __construct(private readonly ClientsDBInterface $clientsDB)
{
}
这是正确的方法吗?每个实现使用自己的实现来处理数据库是否正确?
实现中的逻辑(例如,if、else、switch)是否可以接受?据我了解,所有业务逻辑都应该在用例中。这是真的吗?
哪一层负责循环处理数据?我有一个任务:处理客户一圈,检查他们的更改。执行此操作的逻辑最好放置在哪一层?
客户表结构。为了最大限度地减少表的数量,我将以下字段添加到clients表中:
external_id — 来自外部系统的客户端 ID。 local_id — 当前系统中的客户端 ID。 service — 客户端来自的服务的名称。
逻辑示例:
我从 AExternalClientsRepository 接收数据。我检查客户端是否在数据库中。如果没有客户端,我会在数据库中创建一个 service = AExternal 的条目,然后通过 ACrmClientsRepository 在 CRM 中创建它,并在数据库中添加一个 service = ACrm 的新条目。这种数据结构和实现方法有多健全?
软件设计有一些原则,特别是单一职责原则(SRP)。这意味着该组件必须只关注并仅限于一项任务。如果存储库的任务是从特定数据库检索客户端数据,那么这就是它应该做的。如果他的任务是访问外部服务,那么这就是他应该做的。如果任务是将客户端数据从一个系统传输到另一个系统,则应由一个单独的组件负责此操作,而不是称为存储库(任务、作业、管理器、服务等,具体取决于您的结构)。
本质上,存储库应该只处理必要的 CRUD 操作及其变体/扩展,因为它是纯数据库层。如果我正确理解了逻辑,那么您通过授予存储库从另一个职责读取数据的权限(访问另一个存储库)来违反存储库中的这一原则。
由于未指定语言,我使用类似的伪代码。您的存储库可能如下所示:
具有自己逻辑的单独组件应该负责传输,这可能已经如下所示:
剩下的取决于需求和细节,所以我不能明确地说关于数据库的结构。