干净的建筑 - DTO地方

发布于 2025-02-05 07:34:40 字数 981 浏览 2 评论 0原文

目前,我正在尝试使用干净的体系结构将旧应用程序迁移到某些API。 到目前为止,我能够进行更改,但是每次遇到DTO时,我都无法理解如何将其放置在干净的体系结构中。 通过DTO,我指的是:包含来自域实体的多个属性的对象。我之所以使用DTO,是因为数据库仍处于“传统格式”中,但是API必须揭示多个系统中响应的不同格式。

假设我有以下结构:

域:

public class EntityA
{
    public string Prop1{get; set;}
}
public class EntityB
{
    public string Prop2{get; set;}
}

然后,我有一个服务的接口,如下所示:

public interface IService
{
}

在应用程序层(用例)中,我实现了域和DTO本身中描述的服务:

public class DTO
{
    public string Prop1{get; set;}
    public string Prop2{get; set;}
}

public class Service : IService
{
   public IEnumerable<DTO> RetrieveDto()
   {
        return new DTO()//Construct DTO....
   }
}

这里我的问题是开始。

我需要修改域服务接口以返回DTO。这正在产生圆形参考,我认为可以完成。

我尝试在域中创建一个抽象的DTO类,并从中继承,以避免从域到应用程序的引用。但是我不确定这应该是一个解决方案,因为DTO只是存储数据的对象,我在该抽象类中没有任何内容。

当前,映射器和DTO被放置在应用程序中,因为从应用程序中,我访问了存储库的基础架构,这是我将实体映射到DTO的地方。

所以我的问题是:我在这里理解错误吗? DTO应该在哪里正确的位置?

谢谢你!

Currently I'm trying to migrate legacy application to some api's using Clean Architecture.
Until now I was able to go through changes, but every time I encounter a DTO I cannot understand how to place it in the clean architecture.
By DTO, I am referring to: Object containing multiple properties from domain entities combined. I'm using DTO's because the database is still in "legacy format" but the api must expose diferent formats of responses across multiple systems.

Let's say I have the following structure:

Domain:

public class EntityA
{
    public string Prop1{get; set;}
}
public class EntityB
{
    public string Prop2{get; set;}
}

Then I have a interface to a Service as follow:

public interface IService
{
}

In the application layer (Use Cases) I have the implementation of the services described in the Domain and the DTO itself:

public class DTO
{
    public string Prop1{get; set;}
    public string Prop2{get; set;}
}

public class Service : IService
{
   public IEnumerable<DTO> RetrieveDto()
   {
        return new DTO()//Construct DTO....
   }
}

And here my issue is starting.

I need to modify the domain service interface to return the DTO. This is generating a circular reference and I don't think is ok to be done.

I tried to create an abstract DTO class in the domain and inherit from it to avoid the reference from Domain to Application. But I'm not pretty sure this should be a solution because DTO's are just object that store data, I don't have anything in that abstract class.

Currently, the mapper and the DTO are placed in the Application because from the application I access the Infrastructure for repositories and here is where I map the entity to a DTO.

So my question is: Do I understand something wrong here? Where should be DTO places correctly?

Thank you!

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

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

发布评论

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

评论(6

清风夜微凉 2025-02-12 07:34:40

域层:

  • 模型 - 包含具有与域相关的属性和行为的具体类。它们不依赖于任何东西,它们是域层本身的核心。

  • 服务 - 域服务是具体类,包含不适合域模型的业务规则。

  • 事件 - 仅包含域事件poco。

  • dto-仅包含每个实体和值对象的接口。应在持久性层中作为数据模型实现。

  • 工厂 - 包含接口及其实现,它接受DTO接口以创建域聚集根的新​​实例。将在应用程序层中的持久性层和应用程序服务中使用。

应用层:

  • 存储库 - 持续和获取域聚集根对象的接口。

  • 集成事件 - 包含用于处理集成事件的混凝土Poco clase和接口。用于传播域状态更改为其他应用程序。示例:通过事件总线到其他微型服务或Workser服务。

  • 活动总线 - 实现事件总线的接口。

  • 服务 - 它们是包含用例的应用程序服务。他们精心策划了域层及其自己的层中可用的接口,以完成用例。他们将DTO接口类型返回到外层进行查询。 WebAPI或MVC应用程序中的控制器将消耗这些服务。

  • dto-将数据返回外世界的接口。

  • 映射器 - 包含将域对象映射到DTO对象的接口,反之亦然。将用于应用程序层,表示/API层并在基础架构层中实现。

域层 - 模型:

 namespace Acme.Core.Domain.Identity.Models.AccountAggregate
{
    public class Account : Aggregate<Account, AccountId>
    {
        public Account(AccountId id, string userName, string normalizedUserName, string passwordHash, string concurrencyStamp, string securityStamp, string email, string normalizedEmail, bool emailConfirmed, string phoneNumber, bool phoneNumberConfirmed, bool twoFactorEnabled, DateTimeOffset? lockoutEnd, bool lockoutEnabled, int accessFailedCount, AccountStatus status, List<RoleId> roles, List<AccountClaim> accountClaims, List<AccountLogin> accountLogins, List<AccountToken> accountTokens) : base(id)
        {
            UserName = Guard.Against.NullOrWhiteSpace(userName, nameof(userName));
            NormalizedUserName = Guard.Against.NullOrWhiteSpace(normalizedUserName, nameof(normalizedUserName));
            PasswordHash = Guard.Against.NullOrWhiteSpace(passwordHash, nameof(passwordHash));
            ConcurrencyStamp = concurrencyStamp;
            SecurityStamp = securityStamp;
            Email = Guard.Against.NullOrWhiteSpace(email, nameof(email));
            NormalizedEmail = Guard.Against.NullOrWhiteSpace(normalizedEmail, nameof(normalizedEmail));
            EmailConfirmed = emailConfirmed;
            PhoneNumber = phoneNumber;
            PhoneNumberConfirmed = phoneNumberConfirmed;
            TwoFactorEnabled = twoFactorEnabled;
            LockoutEnd = lockoutEnd;
            LockoutEnabled = lockoutEnabled;
            AccessFailedCount = accessFailedCount;
            Status = Guard.Against.Null(status, nameof(status));
            _roles = Guard.Against.Null(roles, nameof(roles));
            _accountClaims = accountClaims;
            _accountLogins = accountLogins;
            _accountTokens = accountTokens;
        }

        public string UserName { get; private set; }
        public string NormalizedUserName { get; private set; }
        public string PasswordHash { get; private set; }
        public string ConcurrencyStamp { get; private set; }
        public string SecurityStamp { get; private set; }
        public string Email { get; private set; }
        public string NormalizedEmail { get; private set; }
        public bool EmailConfirmed { get; private set; }
        public string PhoneNumber { get; private set; }
        public bool PhoneNumberConfirmed { get; private set; }
        public bool TwoFactorEnabled { get; private set; }
        public DateTimeOffset? LockoutEnd { get; private set; }
        public bool LockoutEnabled { get; private set; }
        public int AccessFailedCount { get; private set; }
        public AccountStatus Status { get; private set; }
        private List<RoleId> _roles;
        public IReadOnlyCollection<RoleId> Roles
        {
            get
            {
                return _roles;
            }
        }
        private List<AccountClaim> _accountClaims;
        public IReadOnlyCollection<AccountClaim> AccountClaims
        {
            get
            {
                return _accountClaims;
            }
        }
        private List<AccountLogin> _accountLogins;
        public IReadOnlyCollection<AccountLogin> AccountLogins
        {
            get
            {
                return _accountLogins;
            }
        }
        private List<AccountToken> _accountTokens;
        public IReadOnlyCollection<AccountToken> AccountTokens
        {
            get
            {
                return _accountTokens;
            }
        }

        public void AddRole(long roleId)
        {
            var role = _roles.Where(x => x.GetValue().Equals(roleId)).FirstOrDefault();

            if (role == null)
            {
                _roles.Add(new RoleId(roleId));
            }
        }

        public void RemoveRole(long roleId)
        {
            var role = _roles.Where(x => x.GetValue().Equals(roleId)).FirstOrDefault();

            if (role == null)
            {
                _roles.Remove(role);
            }
        }

        public void ActivateAccount()
        {
            Status = AccountStatus.Active;
        }

        public void BanAccount()
        {
            Status = AccountStatus.Banned;
        }
        public void CloseAccount()
        {
            Status = AccountStatus.Closed;
        }
        public void LockAccount()
        {
            Status = AccountStatus.Locked;
        }
        public void NewAccount()
        {
            Status = AccountStatus.New;
        }
    }
}

域层 - dto:

namespace Acme.Core.Domain.Identity.DTO
{
    public interface IAccountDto
    {
        long Id { get; set; }
        string UserName { get; set; }
        string NormalizedUserName { get; set; }
        string PasswordHash { get; set; }
        string ConcurrencyStamp { get; set; }
        string SecurityStamp { get; set; }
        string Email { get; set; }
        string NormalizedEmail { get; set; }
        bool EmailConfirmed { get; set; }
        string PhoneNumber { get; set; }
        bool PhoneNumberConfirmed { get; set; }
        bool TwoFactorEnabled { get; set; }
        DateTimeOffset? LockoutEnd { get; set; }
        bool LockoutEnabled { get; set; }
        int AccessFailedCount { get; set; }
        int StatusId { get; set; }
        ICollection<long> Roles { get; set; }
        ICollection<IAccountClaimDto> Claims { get; set; }
        ICollection<IAccountLoginDto> Logins { get; set; }
        ICollection<IAccountTokenDto> Tokens { get; set; }
    }
}

域层 - 工厂:

namespace Acme.Core.Domain.Identity.Factories
{
    public interface IAccountFactory
    {
        Account Create(IAccountDto dto);
        AccountId Create(long id);
    }
}

namespace Acme.Core.Domain.Identity.Factories
{
    public class AccountFactory : IAccountFactory
    {
        private readonly IAccountClaimFactory _accountClaimFactory;
        private readonly IAccountLoginFactory _accountLoginFactory;
        private readonly IAccountTokenFactory _accountTokenFactory;
        private readonly IRoleFactory _roleFactory;

        public AccountFactory(IAccountClaimFactory accountClaimFactory, IAccountLoginFactory accountLoginFactory, IAccountTokenFactory accountTokenFactory, IRoleFactory roleFactory)
        {
            _accountClaimFactory = Guard.Against.Null(accountClaimFactory, nameof(accountClaimFactory));
            _accountLoginFactory = Guard.Against.Null(accountLoginFactory, nameof(accountLoginFactory));
            _accountTokenFactory = Guard.Against.Null(accountTokenFactory, nameof(accountTokenFactory));
            _roleFactory = Guard.Against.Null(roleFactory, nameof(roleFactory));
        }

        public Account Create(IAccountDto dto)
        {
            AccountId aggregateId = Create(dto.Id);

            AccountStatus status;

            if (dto.StatusId.Equals(0))
            {
                status = AccountStatus.New;
            }
            else
            {
                status = AccountStatus.FromValue<AccountStatus>(dto.StatusId);
            }

            List<RoleId> roles = new List<RoleId>();

            foreach (long roleid in dto.Roles)
            {
                roles.Add(_roleFactory.Create(roleid));
            }

            List<AccountClaim> accountClaims = new List<AccountClaim>();

            foreach (var claim in dto.Claims)
            {
                accountClaims.Add(_accountClaimFactory.Create(claim));
            }

            List<AccountLogin> accountLogins = new List<AccountLogin>();

            foreach (var login in dto.Logins)
            {
                accountLogins.Add(_accountLoginFactory.Create(login));
            }

            List<AccountToken> accountTokens = new List<AccountToken>();

            foreach (var token in dto.Tokens)
            {
                accountTokens.Add(_accountTokenFactory.Create(token));
            }

            return new Account(aggregateId, dto.UserName, dto.NormalizedUserName, dto.PasswordHash, dto.ConcurrencyStamp, dto.SecurityStamp, dto.Email, dto.NormalizedEmail, dto.EmailConfirmed, dto.PhoneNumber, dto.PhoneNumberConfirmed, dto.TwoFactorEnabled, dto.LockoutEnd, dto.LockoutEnabled, dto.AccessFailedCount, status, roles, accountClaims, accountLogins, accountTokens);
        }

        public AccountId Create(long id)
        {
            return new AccountId(id);
        }
    }
}

应用程序层 - 存储库:

namespace Acme.Core.Application.Identity.Repositories
{
    public interface IAccountRepo : ICreateRepository<Account>, IReadRepository<Account, AccountId>, IUpdateRepository<Account>
    {

    }
}

应用程序层 - 集成事件:

namespace Acme.Core.Application.Identity.IntegrationEvents.Events
{
    public record AccountCreatedIntegrationEvent : IntegrationEvent
    {
        public AccountCreatedIntegrationEvent(string accountName, string emailAddress, string token)
        {
            AccountName = accountName;
            EmailAddress = emailAddress;
            Token = token;
        }

        public string AccountName { get; }
        public string EmailAddress { get; }
        public string Token { get; }
    }
}

应用程序层 - 应用程序服务:

namespace Acme.Core.Application.Identity.Services
{
    public interface IAccountService
    {
        Task<bool> RegisterAsync(IAccountDto dto);
        Task<bool> ActivateAsync(string emailAddress, string activationCode);
        Task<bool> RecoverUsernameAsync(string emailAddress);
        Task<bool> ResetPasswordAsync(string emailAddress);
        Task<bool> ChangePasswordAsync(string username, string currentPassword, string newPassword);
        Task<bool> CloseAccountAsync(string username);
        Task<bool> LockAccountAsync(string username);
        Task<bool> BanAccountAsync(string username);
        Task<bool> GenerateTokenForExistingEmailAddressAsync(string username);
        Task<bool> ChangeEmailAddressAsync(string username, string activationCode, string newEmailAddress);
        Task<bool> ActivateNewEmailAddressAsync(string emailaddress, string activationCode);
        Task<bool> GenerateTokenForPhoneNumberAsync(string username, string phoneNumber);
        Task<bool> ChangePhoneNumberAsync(string username, string phoneNumber, string activationCode);
    }
}

Domain Layer:

  • Models - contains concrete classes with properties and behaviors relevant to domain. They don't depend on anything they are the core to domain layer itself.

  • Services - domain services are concrete classes which contain business rules which does not fit within a domain model.

  • Events - contain only domain event POCO.

  • DTO - contains only interfaces for each entities and value object. Should be implemented in persistence layer as data models.

  • Factories - contains interfaces and its implementation which accepts DTO interface to create a new instance of domain aggregate root. Would be utilized in persistence layer and application services in application layer.

Application Layer:

  • Repositories - interfaces for persisting and fetch domain aggregate root object.

  • Integration Events - contains concrete POCO clases and interfaces for handling integration event. Use for propagating domain state change to other applications. Example: To other micro services or workser service via event bus.

  • Event Bus - interfaces for implementing event bus.

  • Services - they are application services which contains use cases. They orchestrate the interfaces available within domain layer and its own layer, for accomplishing the use case. They return DTO interface type to outside layers for queries. Controllers in webapi or mvc application will consume these services.

  • DTO - interfaces for returning data to outer world.

  • Mappers - contains interfaces to map a domain object to DTO object and vice versa. Would be utilized in application layer, presentation/api layer and implemented in infrastructure layer.

Domain Layer - Models:

 namespace Acme.Core.Domain.Identity.Models.AccountAggregate
{
    public class Account : Aggregate<Account, AccountId>
    {
        public Account(AccountId id, string userName, string normalizedUserName, string passwordHash, string concurrencyStamp, string securityStamp, string email, string normalizedEmail, bool emailConfirmed, string phoneNumber, bool phoneNumberConfirmed, bool twoFactorEnabled, DateTimeOffset? lockoutEnd, bool lockoutEnabled, int accessFailedCount, AccountStatus status, List<RoleId> roles, List<AccountClaim> accountClaims, List<AccountLogin> accountLogins, List<AccountToken> accountTokens) : base(id)
        {
            UserName = Guard.Against.NullOrWhiteSpace(userName, nameof(userName));
            NormalizedUserName = Guard.Against.NullOrWhiteSpace(normalizedUserName, nameof(normalizedUserName));
            PasswordHash = Guard.Against.NullOrWhiteSpace(passwordHash, nameof(passwordHash));
            ConcurrencyStamp = concurrencyStamp;
            SecurityStamp = securityStamp;
            Email = Guard.Against.NullOrWhiteSpace(email, nameof(email));
            NormalizedEmail = Guard.Against.NullOrWhiteSpace(normalizedEmail, nameof(normalizedEmail));
            EmailConfirmed = emailConfirmed;
            PhoneNumber = phoneNumber;
            PhoneNumberConfirmed = phoneNumberConfirmed;
            TwoFactorEnabled = twoFactorEnabled;
            LockoutEnd = lockoutEnd;
            LockoutEnabled = lockoutEnabled;
            AccessFailedCount = accessFailedCount;
            Status = Guard.Against.Null(status, nameof(status));
            _roles = Guard.Against.Null(roles, nameof(roles));
            _accountClaims = accountClaims;
            _accountLogins = accountLogins;
            _accountTokens = accountTokens;
        }

        public string UserName { get; private set; }
        public string NormalizedUserName { get; private set; }
        public string PasswordHash { get; private set; }
        public string ConcurrencyStamp { get; private set; }
        public string SecurityStamp { get; private set; }
        public string Email { get; private set; }
        public string NormalizedEmail { get; private set; }
        public bool EmailConfirmed { get; private set; }
        public string PhoneNumber { get; private set; }
        public bool PhoneNumberConfirmed { get; private set; }
        public bool TwoFactorEnabled { get; private set; }
        public DateTimeOffset? LockoutEnd { get; private set; }
        public bool LockoutEnabled { get; private set; }
        public int AccessFailedCount { get; private set; }
        public AccountStatus Status { get; private set; }
        private List<RoleId> _roles;
        public IReadOnlyCollection<RoleId> Roles
        {
            get
            {
                return _roles;
            }
        }
        private List<AccountClaim> _accountClaims;
        public IReadOnlyCollection<AccountClaim> AccountClaims
        {
            get
            {
                return _accountClaims;
            }
        }
        private List<AccountLogin> _accountLogins;
        public IReadOnlyCollection<AccountLogin> AccountLogins
        {
            get
            {
                return _accountLogins;
            }
        }
        private List<AccountToken> _accountTokens;
        public IReadOnlyCollection<AccountToken> AccountTokens
        {
            get
            {
                return _accountTokens;
            }
        }

        public void AddRole(long roleId)
        {
            var role = _roles.Where(x => x.GetValue().Equals(roleId)).FirstOrDefault();

            if (role == null)
            {
                _roles.Add(new RoleId(roleId));
            }
        }

        public void RemoveRole(long roleId)
        {
            var role = _roles.Where(x => x.GetValue().Equals(roleId)).FirstOrDefault();

            if (role == null)
            {
                _roles.Remove(role);
            }
        }

        public void ActivateAccount()
        {
            Status = AccountStatus.Active;
        }

        public void BanAccount()
        {
            Status = AccountStatus.Banned;
        }
        public void CloseAccount()
        {
            Status = AccountStatus.Closed;
        }
        public void LockAccount()
        {
            Status = AccountStatus.Locked;
        }
        public void NewAccount()
        {
            Status = AccountStatus.New;
        }
    }
}

Domain Layer - DTO:

namespace Acme.Core.Domain.Identity.DTO
{
    public interface IAccountDto
    {
        long Id { get; set; }
        string UserName { get; set; }
        string NormalizedUserName { get; set; }
        string PasswordHash { get; set; }
        string ConcurrencyStamp { get; set; }
        string SecurityStamp { get; set; }
        string Email { get; set; }
        string NormalizedEmail { get; set; }
        bool EmailConfirmed { get; set; }
        string PhoneNumber { get; set; }
        bool PhoneNumberConfirmed { get; set; }
        bool TwoFactorEnabled { get; set; }
        DateTimeOffset? LockoutEnd { get; set; }
        bool LockoutEnabled { get; set; }
        int AccessFailedCount { get; set; }
        int StatusId { get; set; }
        ICollection<long> Roles { get; set; }
        ICollection<IAccountClaimDto> Claims { get; set; }
        ICollection<IAccountLoginDto> Logins { get; set; }
        ICollection<IAccountTokenDto> Tokens { get; set; }
    }
}

Domain Layer - Factories:

namespace Acme.Core.Domain.Identity.Factories
{
    public interface IAccountFactory
    {
        Account Create(IAccountDto dto);
        AccountId Create(long id);
    }
}

namespace Acme.Core.Domain.Identity.Factories
{
    public class AccountFactory : IAccountFactory
    {
        private readonly IAccountClaimFactory _accountClaimFactory;
        private readonly IAccountLoginFactory _accountLoginFactory;
        private readonly IAccountTokenFactory _accountTokenFactory;
        private readonly IRoleFactory _roleFactory;

        public AccountFactory(IAccountClaimFactory accountClaimFactory, IAccountLoginFactory accountLoginFactory, IAccountTokenFactory accountTokenFactory, IRoleFactory roleFactory)
        {
            _accountClaimFactory = Guard.Against.Null(accountClaimFactory, nameof(accountClaimFactory));
            _accountLoginFactory = Guard.Against.Null(accountLoginFactory, nameof(accountLoginFactory));
            _accountTokenFactory = Guard.Against.Null(accountTokenFactory, nameof(accountTokenFactory));
            _roleFactory = Guard.Against.Null(roleFactory, nameof(roleFactory));
        }

        public Account Create(IAccountDto dto)
        {
            AccountId aggregateId = Create(dto.Id);

            AccountStatus status;

            if (dto.StatusId.Equals(0))
            {
                status = AccountStatus.New;
            }
            else
            {
                status = AccountStatus.FromValue<AccountStatus>(dto.StatusId);
            }

            List<RoleId> roles = new List<RoleId>();

            foreach (long roleid in dto.Roles)
            {
                roles.Add(_roleFactory.Create(roleid));
            }

            List<AccountClaim> accountClaims = new List<AccountClaim>();

            foreach (var claim in dto.Claims)
            {
                accountClaims.Add(_accountClaimFactory.Create(claim));
            }

            List<AccountLogin> accountLogins = new List<AccountLogin>();

            foreach (var login in dto.Logins)
            {
                accountLogins.Add(_accountLoginFactory.Create(login));
            }

            List<AccountToken> accountTokens = new List<AccountToken>();

            foreach (var token in dto.Tokens)
            {
                accountTokens.Add(_accountTokenFactory.Create(token));
            }

            return new Account(aggregateId, dto.UserName, dto.NormalizedUserName, dto.PasswordHash, dto.ConcurrencyStamp, dto.SecurityStamp, dto.Email, dto.NormalizedEmail, dto.EmailConfirmed, dto.PhoneNumber, dto.PhoneNumberConfirmed, dto.TwoFactorEnabled, dto.LockoutEnd, dto.LockoutEnabled, dto.AccessFailedCount, status, roles, accountClaims, accountLogins, accountTokens);
        }

        public AccountId Create(long id)
        {
            return new AccountId(id);
        }
    }
}

Application Layer - Repositories:

namespace Acme.Core.Application.Identity.Repositories
{
    public interface IAccountRepo : ICreateRepository<Account>, IReadRepository<Account, AccountId>, IUpdateRepository<Account>
    {

    }
}

Application Layer - Integration Events:

namespace Acme.Core.Application.Identity.IntegrationEvents.Events
{
    public record AccountCreatedIntegrationEvent : IntegrationEvent
    {
        public AccountCreatedIntegrationEvent(string accountName, string emailAddress, string token)
        {
            AccountName = accountName;
            EmailAddress = emailAddress;
            Token = token;
        }

        public string AccountName { get; }
        public string EmailAddress { get; }
        public string Token { get; }
    }
}

Application Layer - Application Services:

namespace Acme.Core.Application.Identity.Services
{
    public interface IAccountService
    {
        Task<bool> RegisterAsync(IAccountDto dto);
        Task<bool> ActivateAsync(string emailAddress, string activationCode);
        Task<bool> RecoverUsernameAsync(string emailAddress);
        Task<bool> ResetPasswordAsync(string emailAddress);
        Task<bool> ChangePasswordAsync(string username, string currentPassword, string newPassword);
        Task<bool> CloseAccountAsync(string username);
        Task<bool> LockAccountAsync(string username);
        Task<bool> BanAccountAsync(string username);
        Task<bool> GenerateTokenForExistingEmailAddressAsync(string username);
        Task<bool> ChangeEmailAddressAsync(string username, string activationCode, string newEmailAddress);
        Task<bool> ActivateNewEmailAddressAsync(string emailaddress, string activationCode);
        Task<bool> GenerateTokenForPhoneNumberAsync(string username, string phoneNumber);
        Task<bool> ChangePhoneNumberAsync(string username, string phoneNumber, string activationCode);
    }
}
最美的太阳 2025-02-12 07:34:40

DTO是数据传输对象。当涉及网络调用时,应该使用它们,因为它们是轻量级的。实体可能很重,并且包含域逻辑,这可能不是通过网络传输的必要条件。 DTO仅用于传递数据而无需公开您的域实体。例如,当您的API返回对客户端应用程序的响应时,请使用DTO。

由于您的域服务将在域层中,因此您可以直接使用您的实体。我认为这不是DTO的正确用例。您可以正确地将DTO及其映射放在应用层中。域层绝不应与外界直接通信。

DTOs are Data Transfer Objects. They should be used when there is a network call involved because they are lightweight. Entities can be heavy and contain domain logic which may not be necessary to be transmitted over a network. DTOs are used to only pass data without exposing your domain entities. Say for example, when your API returns a response to a client app, make use of a DTO.

Since your domain service will be in the Domain layer, you can directly make use of your entities. I don't think this is a right use case for a DTO. You are correct to place your DTOs and their mappings in the Application layer. The Domain layer should never directly communicate with the outside world.

空城旧梦 2025-02-12 07:34:40

因此,经过几周的尝试了解这一点,我可以说以下内容。

这是鲍勃叔叔在演讲中提出的图表之一。
传递机制(在API中是控制器)正在通过边界(接口)与交互式(用例 /服务)对话。
这意味着请求/响应必须特定于用例。

如果我理解错误的话,请纠正我:)

谢谢!

So after couple of weeks of trying to understand this I can say the following.

enter image description here

This is one of the diagram presented by uncle bob in a lecture.
The delivery mechanism (which in an API is the controller) is talking with the interactor (Use case / Service) through the boundary (Interface).
This means that the Request/Response must be specific to a use-case.

Please correct me if I understood wrong :)

Thank you!

眼前雾蒙蒙 2025-02-12 07:34:40

您必须与服务接口一起定义域层中的DTO。 DTO本质上是服务接口定义的一部分,没有它,它没有任何意义。

当您考虑该服务的实现,在外层中,尽管内部实施不同,这些实现都将具有返回DTO类型的能力。

取决于服务定义的接口的域层方法还取决于贴上的DTO作为服务的方法返回类型。

类似:

public class Domain.DTO
{
    public string Prop1{get; set;}
    public string Prop2{get; set;}
}

public interface Domain.IService
{
   DTO DoSomething();
}

public class Domain.EntityA
{
    public string Prop1{get; set;}

    // Super-contrived, but you get the point...
    public void DoSomethingWith(IService service)
    {
        // regardless of the actual implementation,
        // I know result will always be DTO-shaped
        var result = service.DoSomething();
    }
}

public class Application.ServiceOne : Domain.IService
{
    public Domain.DTO DoSomething()
    {
        // Do something one way, but providing the agreed DTO
    }
}

public class Application.ServiceTwo : Domain.IService
{
    public Domain.DTO DoSomething()
    {
        // Do something another way, but still providing the agreed DTO
    }
}

这维护着建筑所推广的所有依赖性。

You must define the DTO in the domain layer, along with the service's interface. The DTO is essentially part of the service's interface definition and it doesn't make any sense without it.

When you think about implementations of that service, in outer layers, those implementations would all share the ability to return that DTO type, despite different internal implementations.

The domain layer methods that depend on the service's defined interface also depend on the definded DTO as the service's method(s) return type.

Something like:

public class Domain.DTO
{
    public string Prop1{get; set;}
    public string Prop2{get; set;}
}

public interface Domain.IService
{
   DTO DoSomething();
}

public class Domain.EntityA
{
    public string Prop1{get; set;}

    // Super-contrived, but you get the point...
    public void DoSomethingWith(IService service)
    {
        // regardless of the actual implementation,
        // I know result will always be DTO-shaped
        var result = service.DoSomething();
    }
}

public class Application.ServiceOne : Domain.IService
{
    public Domain.DTO DoSomething()
    {
        // Do something one way, but providing the agreed DTO
    }
}

public class Application.ServiceTwo : Domain.IService
{
    public Domain.DTO DoSomething()
    {
        // Do something another way, but still providing the agreed DTO
    }
}

This maintains all dependencies travelling inwards as promoted by the architecture.

萌吟 2025-02-12 07:34:40

我认为您看到DTO与实体如此分开是不准确的。毕竟,如果您的用例需要返回此数据结构,则属于 /下的用例。

旁注:我也不喜欢“ dto”一词,因为这并未指定任何与众不同的内容。 (几乎所有对象都包含数据并已传输),但要转到您的用例:
我将DTO重命名为“ Usecasexresponse”,然后将其放在其他实体旁边。然后,所有的ENTITES都将由一些面向输入的输入,一些面向输出的输入,也许还有一些通用的输入。
逻辑,如何在用例类中转换为输入的输入。

如果您认为此数据组合在您的业务逻辑中没有位置,那么您需要将其他entites暴露于外层,并使用该外层将响应汇总到DTO中。

I think it is not accurate that you see the DTO so separate from the entities. After all, if your use case needs to return this data structure, it belongs to / under the use case.

Side note: I also dislike the term "dto" as this does not specify anything out of the ordinary. (Nearly all objects contain data and are transferred) But on to your use case:
I would rename the DTO to "UseCaseXResponse" and then put it next to the other entities. All the entites would then consist of some input-oriented ones, some output-oriented ones and maybe also some general purpose ones.
The logic, how to convert the input ones to the output ones is in the use case class.

If you feel that this data agglomeration has no place in your business logic, then you need to expose other entites to the outer layer and use that outer layer to aggregate the response into a dto.

把时间冻结 2025-02-12 07:34:40
  • 最受欢迎的体系结构风格:第1层

    • 实体(实体/模型)=&gt;您可以将DTO放在DTO文件夹中
    • 数据访问(您检索数据)=&gt;您可以在这里与DTO绘制DTO的映射
      -business =&gt;存储库,服务
    • core =&gt;助手方法常见事物
    • api =&gt;控制器,与前面又称客户端的通信
  • Most popular architecture style : Layer 1

    • Entity (Entities/Models) => You can keep dto's here in a DTO'S folder
    • Data Access (You retrieve data) => You can map dto's here with entities here
      -Business => Repositories , Services
    • Core => Helper methods common things
    • API => Controllers,Communication with fronted aka client
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文