DDD:需要在应用程序服务层,域服务或域对象的业务逻辑?

发布于 2025-02-11 09:52:43 字数 4183 浏览 1 评论 0原文

对于需要验证的属性,可以说,对于一个我们拥有乡村领域的实体, 根据域专家要求的某些业务逻辑,必须将该国家领域验证为Alpha-3代码。

注意:

*我们需要坚持这个国家数据,因为它也可以具有其他价值,并且将来可能会增加,更新和删除该国的持续数据。

这只是使用可能很少会改变的国家代码的一个示例。

另一个有效的示例可以是用户创建独特且有效的域电子邮件检查的用户,这将需要从持久性中进行唯一性检查 *

案例1。

在应用程序层中进行验证:

如果我们调用存储库country> countrybycountrybycountryaltryalpha3code()在应用程序层中,则该值是正确的然后,系统的有效部分我们可以传递createValidentity(),如果没有,则可以将错误直接在应用程序层用例中丢弃。

问题:

  • 如果需要在其他用例中检查相同的验证,如果其应用层关注
  • 此处的业务逻辑现在是应用程序服务层的一部分

案例2

在其价值对象类中验证国家代码或在域层中的域服务,

这样做将保持业务逻辑在域内层,也不会违反干燥原则。

import { ValueObject } from '@shared/core/domain/ValueObject';
    import { Result } from '@shared/core/Result';
    import { Utils } from '@shared/utils/Utils';
    
    interface CountryAlpha3CodeProps {
      value: string;
    }
    
    export class CountryAlpha3Code extends ValueObject<CountryAlpha3CodeProps> {
      // Case Insensitive String. Only printable ASCII allowed. (Non-printable characters like: Carriage returns, Tabs, Line breaks, etc are not allowed)
    
      get value(): string {
        return this.props.value;
      }
    
      private constructor(props: CountryAlpha3CodeProps) {
        super(props);
      }
    
      public static create(value: string): Result<CountryAlpha3Code> {
        
        
        return Result.ok<CountryAlpha3Code>(new CountryAlpha3Code({ value: value }));
      }
    }
  • 从内部域层调用存储库(服务)是否很好 或vo(不建议使用))然后依赖性流将改变?

  • 如果我们触发事件如何使其同步?

  • 有哪些更好的解决方法?



export default class UseCaseClass implements IUseCaseInterface {
  constructor(private readonly _repo: IRepo, private readonly countryCodeRepo: ICountryCodeRepo) {}

  async execute(request: dto): Promise<dtoResponse> {
    const someOtherKeyorError = KeyEntity.create(request.someOtherDtoKey);
    const countryOrError = CountryAlpha3Code.create(request.country);
    
        const dtoResult = Result.combine([
          someOtherKeyorError, countryOrError
        ]);
    
        if (dtoResult.isFailure) {
          return left(Result.fail<void>(dtoResult.error)) as dtoResponse;
        }
    
        try {
          // -> Here we are just calling the repo
           const isValidCountryCode = await this.countryCodeRepo.getCountryCodeByAlpha2Code(countryOrError.getValue()); // return boolean value
    
          if (!isValidCountryCode) {
           return left(new ValidCountryCodeError.CountryCodeNotValid(countryOrError.getValue())) as dtoResponse;
        }
    
          const dataOrError = MyEntity.create({...request,
            key: someOtherKeyorError.city.getValue(),
            country: countryOrError.getValue(),
          });
    
     
          const commandResult = await this._repo.save(dataOrError.getValue());
    
          return right(Result.ok<any>(commandResult));
        } catch (err: any) {
          return left(new AppError.UnexpectedError(err)) as dtoResponse;
        }
      }
    }

在上面的应用层中,

代码的这一部分:


  const isValidCountryCode = await this.countryCodeRepo.getCountryCodeByAlpha2Code(countryOrError.getValue()); // return boolean value
       
  if (!isValidCountryCode) {
   return left(new ValidCountryCodeError.CountryCodeNotValid(countryOrError.getValue())) as dtoResponse;
   }

它可以调用countrycoderepo并获取结果,否则该部分应移至域服务,然后检查countrycode vo的有效性?

更新:

探索后,我发现此作者:Vladimir Khorikov似乎与我所寻找的东西很近,他关注

”在此处输入图像描述

根据他的想法其他用例呼叫不知道该特定的VO/实体创建是必需的。

我仍然为正确的方法感到困惑

For an attribute which need to be validated, lets say for an entity we have country field as VO
This country field needs to be validated to be alpha-3 code as per some business logic required by domain expert.

NOTE:

*We need to persist this country data as it can have other values also and possible in future there can be addition, updating and deleting of the country persisted data.

This is just one example using country code which may rarely change, there can be other fields which needs to be validated from persistence like validating some quantity with wrt data in persistence and it won't be efficient to store them in memory or prefetching them all.

Another valid example can be user creation with unique and valid domain email check, which will need uniqueness check from persistence
*

Case 1.

Doing validation in application layer:

If we call repository countryRepo.getCountryByCountryAlpha3Code() in application layer and then if the value is correct and valid part of system we can then pass the createValidEntity() and if not then can throw the error directly in application layer use-case.

Issue:

  • This validation will be repeated in multiple use-case if same validation need to be checked in other use-cases if its application layer concern
  • Here the business logic is now a part of application service layer

Case 2

Validating the country code in its value object class or domain service in Domain Layer

Doing this will keep business logic inside domain layer and also won't violate DRY principle.

import { ValueObject } from '@shared/core/domain/ValueObject';
    import { Result } from '@shared/core/Result';
    import { Utils } from '@shared/utils/Utils';
    
    interface CountryAlpha3CodeProps {
      value: string;
    }
    
    export class CountryAlpha3Code extends ValueObject<CountryAlpha3CodeProps> {
      // Case Insensitive String. Only printable ASCII allowed. (Non-printable characters like: Carriage returns, Tabs, Line breaks, etc are not allowed)
    
      get value(): string {
        return this.props.value;
      }
    
      private constructor(props: CountryAlpha3CodeProps) {
        super(props);
      }
    
      public static create(value: string): Result<CountryAlpha3Code> {
        
        
        return Result.ok<CountryAlpha3Code>(new CountryAlpha3Code({ value: value }));
      }
    }
  • Is it good to call the repository from inside domain layer (Service
    or VO (not recommended) ) then dependency flow will change?

  • If we trigger event how to make it synchronous?

  • What are some better ways to solve this?



export default class UseCaseClass implements IUseCaseInterface {
  constructor(private readonly _repo: IRepo, private readonly countryCodeRepo: ICountryCodeRepo) {}

  async execute(request: dto): Promise<dtoResponse> {
    const someOtherKeyorError = KeyEntity.create(request.someOtherDtoKey);
    const countryOrError = CountryAlpha3Code.create(request.country);
    
        const dtoResult = Result.combine([
          someOtherKeyorError, countryOrError
        ]);
    
        if (dtoResult.isFailure) {
          return left(Result.fail<void>(dtoResult.error)) as dtoResponse;
        }
    
        try {
          // -> Here we are just calling the repo
           const isValidCountryCode = await this.countryCodeRepo.getCountryCodeByAlpha2Code(countryOrError.getValue()); // return boolean value
    
          if (!isValidCountryCode) {
           return left(new ValidCountryCodeError.CountryCodeNotValid(countryOrError.getValue())) as dtoResponse;
        }
    
          const dataOrError = MyEntity.create({...request,
            key: someOtherKeyorError.city.getValue(),
            country: countryOrError.getValue(),
          });
    
     
          const commandResult = await this._repo.save(dataOrError.getValue());
    
          return right(Result.ok<any>(commandResult));
        } catch (err: any) {
          return left(new AppError.UnexpectedError(err)) as dtoResponse;
        }
      }
    }

In above application layer,

this part of code :


  const isValidCountryCode = await this.countryCodeRepo.getCountryCodeByAlpha2Code(countryOrError.getValue()); // return boolean value
       
  if (!isValidCountryCode) {
   return left(new ValidCountryCodeError.CountryCodeNotValid(countryOrError.getValue())) as dtoResponse;
   }

it it right to call the countryCodeRepo and fetch result or this part should be moved to domain service and then check the validity of the countryCode VO?

UPDATE:

After exploring I found this article by Vladimir Khorikov which seems close to what I was looking, he is following

enter image description here

As per his thoughts some domain logic leakage is fine, but I feel it will still keep the value object validation in invalid state if some other use case call without knowing that persistence check is necessary for that particular VO/entity creation.

I am still confused for the right approach

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

寻找我们的幸福 2025-02-18 09:52:43

我认为,从字符串到ValueObject的转换根本不属于业务逻辑。业务逻辑具有从外部调用的公共合同(可能是API层或演示层)。合同应该已经期望价值对象,而不是原始字符串。因此,任何称为业务逻辑的人都必须弄清楚如何获得这些价值对象。

关于国家代码价值对象的实施,我会质疑是否真的有必要从数据库加载国家代码。国家代码列表很少发生变化。我过去解决这个问题的方法只是简单地将值对象本身内的国家代码列表进行了硬编码。

伪C#中的示例代码,但是您应该明白:

public class CountryCode : ValueObject
{
   // Static definitions to be used in code like:
   // var myCountry = CountryCode.France;
   public static readonly CountryCode France = new CountryCode("FRA");
   public static readonly CountryCode China = new CountryCode("CHN");
   [...]

   public static AllCountries = new [] {
      France, China, ...
   }
   
   public string ThreeLetterCode { get; }
   
   private CountryCode(string threeLetterCountryCode)
   {
      ThreeLetterCode = threeLetterCountryCode;
   }

   public static CountryCode Parse(string code)
   {
      [...] handle nulls, empties, etc

      var exists = AllCountries.FirstOrDefault(c=>c.ThreeLetterCode==code);
      if(exists == null)
         // throw error

      return exists;
   }

}

遵循此方法,您可以制作一个非常有用且对开发人员友好的countrycode Value对象。在我的实际解决方案中,我仅出于记录目的而使用英语的2个和3个字母的代码和显示名称(出于演示目的,演示层可以根据代码查找翻译)。

如果从数据库加载国家代码对于您的方案很有价值,那么列表仍然很可能会很少发生变化,因此您可以在应用程序启动时在值对象本身中加载静态列表应用程序运行很长时间。

In my opinion, the conversion from String to ValueObject does not belong to the Business Logic at all. The Business Logic has a public contract that is invoked from the outside (API layer or presentation layer maybe). The contract should already expect Value Objects, not raw strings. Therefore, whoever is calling the business logic has to figure out how to obtain those Value Objects.

Regarding the implementation of the Country Code value object, I would question if it is really necessary to load the country codes from the database. The list of country codes very rarely changes. The way I've solved this in the past is simply hardcoding the list of country codes inside the value object itself.

Sample code in pseudo-C#, but you should get the point:

public class CountryCode : ValueObject
{
   // Static definitions to be used in code like:
   // var myCountry = CountryCode.France;
   public static readonly CountryCode France = new CountryCode("FRA");
   public static readonly CountryCode China = new CountryCode("CHN");
   [...]

   public static AllCountries = new [] {
      France, China, ...
   }
   
   public string ThreeLetterCode { get; }
   
   private CountryCode(string threeLetterCountryCode)
   {
      ThreeLetterCode = threeLetterCountryCode;
   }

   public static CountryCode Parse(string code)
   {
      [...] handle nulls, empties, etc

      var exists = AllCountries.FirstOrDefault(c=>c.ThreeLetterCode==code);
      if(exists == null)
         // throw error

      return exists;
   }

}

Following this approach, you can make a very useful and developer-friendly CountryCode value object. In my actual solution, I had both the 2 and 3-letter codes and display names in English only for logging purposes (for presentation purposes, the presentation layer can look up the translation based on the code).

If loading the country codes from the DB is valuable for your scenario, it's still very likely that the list changes very rarely, so you could for example load a static list in the value object itself at application start up and then refresh it periodically if the application runs for very long.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文