如何正确实施存储库适配器?

Posted

技术标签:

【中文标题】如何正确实施存储库适配器?【英文标题】:How to correct implement the Repository Adapter? 【发布时间】:2021-10-05 13:43:41 【问题描述】:

我开始学习如何基于 TypeScript 和 NestJS 构建 Clean Architecture。在我开始实现存储库适配器和控制器之前,一切都很好。主要问题是 API 方法和用例的返回类型不兼容。

想法是将实体和用例放在 core 文件夹中,其中用例使用存储库适配器(通过 DI)。此适配器也实现了 core 文件夹中的存储库接口。

app 中包含的存储库适配器的实现。 app 还包含 NestJS 实现、TypeOrm 实体等。但我也想为一些控制器使用存储库,例如 getAll query

!!!还有问题!!!

对于几乎每个命令,我都必须使用Mappers,因为 TypeORM 实体和域实体是不兼容的类型。我认为如果我们将这些数据传递给用例就可以了,因为我们需要将数据从 TypeOrm 形状转换为域形状。

但是如果我只是在控制器中调用存储库适配器方法,我需要再次将数据映射回来......而且我不知道如何跳过不必要的步骤。 在我的想象中,我可以在app服务中调用repository方法,就是这样。 如果我跳过映射,那么所有数据属性都将具有前缀 _(

也许有人遇到过同样的问题?

//核心区

帐户实体 (/domain/account):

export type AccountId = string;
export class Account 
constructor(
  private readonly _id: AccountId,
  private readonly _firstName: string
) 

  get id(): AccountId 
   return this._id;
  

  get firstName() 
   return this._firstName;
  

存储库接口(@98​​7654326@):

import  Account  from '../domains/account';

export interface AccountRepository 
  getAccountById(id: string): Promise<Account>;
  getAllAccounts(): Promise<Account[]>;

存储库用例示例:

import  AccountRepository  from '../../repositories/account-repository';

export class ToDoSomething 
  constructor(private readonly _accountRepository: AccountRepository) 

  async doSomethingWithAccount(command): Promise<boolean> 
    const account = await this._accountRepository.getAccountById(
      command.accountId,
    );

    if (!account) 
      return false;
    

    return true;
  

//应用专区

存储库适配器:

import  Injectable  from '@nestjs/common';
import  InjectRepository  from '@nestjs/typeorm';
import  Repository  from 'typeorm';
import  Account  from '../../../../core/domains/account';
import  AccountRepository  from '../../../../core/repositories/account-repository';
import  AccountEntity  from '../account.entity';
import  AccountMapper  from '../account.mapper';

@Injectable()
export class AccountRepositoryAdapter implements AccountRepository 
  constructor(
    @InjectRepository(AccountEntity)
    private readonly _accountRepository: Repository<AccountEntity>,
  ) 

  async getAccountById(id: string): Promise<Account> 
    return this._accountRepository.findOne( id: id );
    // will return  id: 1, firstName: "name" 
    // and because I need to use MapToDomain
  

  async getAllAccounts(): Promise<Account[]> 
    return this._accountRepository.find();
    // Here too I need to use MapToDomain for every item
  

TypeOrm 帐户:

import 
  Column,
  Entity,
  PrimaryGeneratedColumn,
 from 'typeorm';

@Entity( name: 'account' )
export class AccountEntity 
  @PrimaryGeneratedColumn()
  id: string;

  @Column()
  firstName: string;

【问题讨论】:

【参考方案1】:

在 Clean Architecture 中,控制和数据流通常是这样的:控制器从视图(例如 Web 应用程序)获取请求并将其转换为请求模型,然后传递给用例。用例从请求模型中读取它应该计算的内容并使用存储库与域实体交互以最终创建响应模型。然后将响应模型传递给演示者(可能与控制器属于同一类),后者将其转换为视图的响应。

控制器通常不与域实体甚至 ORM 类型交互。

查看我关于实施清洁架构的博客系列了解更多详情:http://www.plainionist.net/Implementing-Clean-Architecture-Controller-Presenter/

【讨论】:

太棒了。我以为我正在使用数据映射做额外的步骤。但无论如何,我们应该将请求数据形成域形式。当然,需要重新形成。我们没有其他办法。你能建议为几乎每个 API 调用创建用例吗?像 GET:/get-all-users 等等。我得到这个问题的原因是。如果我们不需要做一些领域逻辑,我们应该转换列表中的每个项目吗?因为在我看来我们可以避免它,但我不知道该怎么做..(也许我们可以跳过转换为域模型并立即将数据传递给 Presenter。当然,这是正确的? 根据我的经验,“捷径”在项目开始或几乎不需要响应请求的逻辑时是可以的。随着时间的推移,项目通常会变得更加复杂,然后应该删除捷径,然后真的每个请求都会成为我们的用例。

以上是关于如何正确实施存储库适配器?的主要内容,如果未能解决你的问题,请参考以下文章

如何在回收器视图适配器中获取上下文

适配器在使用 Fresco 库时显示错误的图像

OpenHarmony移植:如何适配utils子系统之KV存储部件

如何在场景动画中正确处理 RecyclerView 适配器

如何在列表适配器中正确使用 ViewHolder 和自定义视图

在适配器 kotlin 中更新视图时如何正确使用 Diff utils