typescript - Design condus de domeniu (DDD) - Pot serviciile de domeniu să aibă apeluri de procedură la distanță?


Lucrez la un proiect DDD și am un UseCase (ApplicationService) care trebuie să implementeze un proiect în Kubernetes. Deci, am DeployProjectUseCase cu intrări (projectId, databaseType) și mai întâi trebuie să verifice tipul de bază de date pe care aplicația îl folosește pentru a crea pod-urile specifice, secretele etc. Va invoca serviciul extern Kubernetes, dar în funcție de tip trebuie să folosească anumite metode specifice ale acestuia. M-am gândit că această logică nu ar trebui să fie în useCase și să fie poluată, ci să folosesc un DomainService care va injecta prin dependență serviciul extern. Nu știu dacă Domain Services pot avea apeluri de procedură la distanță, dar am găsit asta în cartea lui Vaughn Vernon:

There are times when a Domain Service is concerned with remote invocations on
a foreign Bounded Context (2). Yet, the focus here is different in that the Domain
Service is not itself providing a remote procedure call interface but is rather the
client of the RPC.

Așa că m-am gândit la o implementare ca aceasta:

Am o Fabrică care returnează Serviciul de Domeniu corespunzător în funcție de tipul bazei de date. DeployProjectUseCase apelează acest lucru și delegă sarcina către serviciul de domeniu. Am creat un NetworkServicePort și un adaptor care folosește biblioteca Kubernetes pentru Nodejs, pentru a fi injectat în useCase și apoi în Serviciul de Domeniu:

PortServiciuRețea

export interface NetworkServicePort {
  createSecret(namespace: string, body: TCreateSecretBody): Promise<void>;
  createStatefulSet(namespace: string, body: TCreateStatefulSetBody): Promise<void>;
  createConfigMap(...): Promise<void>;
  createPersistentVolume(...): Promise<void>;
  ...
}

UseCaseDeImplementareProiect

export class DeployProjectUseCase {
 constructor(
    private projectRepo: ProjectWriteRepoPort,
    private networkService: NetworkServicePort,
  ) {}
  
  async execute(requestDTO: DeployProjectRequestDTO): Promise<DeployProjectUseCaseResponse> {
    const projectFound = await this.projectRepo.getById(projectId);
    if (!projectFound) {
      return fail(...);
    }

    const domainNetworkService = DomainNetworkServiceFactory.createDomainNetworkService(requestDTO.databaseType, this.networkService);
    await domainNetworkService.deploy(projectFound);
    
    ...

    await this.projectRepo.update(projectFound);

    return ok();
  }
}

FabricaDeServiciiRețeaDomeniu

export interface DomainNetworkService {
  deploy(project: ProjectEntity): Promise<void>;
}

export class DomainNetworkServiceFactory {
  static createDomainNetworkService(
    type: string,
    networkService: NetworkServicePort,
  ): DomainNetworkService {
    switch (type) {
      case 'mongo': {
        return new MongoNetworkService(networkService);
      }
      case 'postgreSQL': {
        return new PostgreSQLNetworkService(networkService);
      }
      default: {
        ...
      }
    }
  }
}

ServiciuRețeaMongo

export class MongoNetworkService implements DomainNetworkService {
  constructor(private networkService: NetworkServicePort) {}

  public async deploy(project: ProjectEntity): Promise<void> {
    // here have a mapper from Project aggregate to the DTO that service accepts
    await this.networkService.createNamespace(...);
    generatePassword(); // generate username and password from a library
    await this.networkService.createSecret(...);
    ...
    await this.networkService.createStatefulSet(...);
    ...
    const infraNetworkService = InfraNetworkServiceEntity.create({
      type: INFRA_NETWORK_SERVICE_TYPES.MONGO,
    });
    project.addInfraNetworkService(infraNetworkService);
  }
}

ServiciuDeRețeaPostgreSQL

export class PostgreSQLNetworkService implements DomainNetworkService {
  constructor(private networkService: NetworkServicePort) {}

  public async deploy(project: ProjectEntity): Promise<void> {
    // here have a mapper from Project aggregate to the DTO that service accepts
    await this.networkService.createNamespace(...);
    await this.networkService.createConfigMap(...);
    await this.networkService.createPersistentVolume(...);
    ...
    const infraNetworkService = InfraNetworkServiceEntity.create({
      type: INFRA_NETWORK_SERVICE_TYPES.POSTGRESQL,
    });
    project.addInfraNetworkService(infraNetworkService);
  }
}

Mi se pare că cele de mai sus au o logică de domeniu care ar putea fi într-un Serviciu de Domeniu, dar poate un Serviciu de Domeniu să aibă un apel de procedură la distanță? Altfel, această logică ar putea fi într-un Serviciu de Aplicație, dar mi se pare greșit și ar fi poluat. Are sens ceva de genul acesta? Cum ai implementa asta cu DDD? Orice idee ar fi cu adevărat utilă.

Mulțumesc anticipat!

Răspuns #1

Din punctul meu de vedere, MongoNetworkService și PostgreSQLNetworkService nu sunt nici servicii de domeniu, nici servicii de aplicație. Ele sunt mai degrabă servicii de infrastructură. De aceea, designul este corect. Singura problemă pe care o pot vedea aici este linia

project.addInfraNetworkService(infraNetworkService)

Nu am putut înțelege clar dacă „proiect” din această propoziție este obiectul tău de domeniu; dacă da, acest apel nu ar trebui să fie expus serviciului de infrastructură, ci ar trebui gestionat la un nivel superior.