我有一个值对象LoginAuth
其中包含辅助登录系统的User
身份验证数据。
对于每个User
,选择辅助登录是可选的。因此,User
实体不保存LoginAuth
值对象,而是LoginAuth
值对象包含它所属的User
实体。
由于我的数据库已规范化,因此我将此值对象存储在一个单独的表中,其中user_id
是主键(以确保唯一性(。
如您所见,我的价值对象不存在于实体内,而是独立存在,但它确实包含它所属的实体。我的问题是:
-
价值对象可以在不存在于实体内部的情况下存在吗?
-
也许这需要是一个实体?
每个
LoginAuth
都应该是唯一的(每个LoginAuth
只允许一个唯一的User
(,所以这个 VO 不会有任何相等的。
注: 我的域不包含此登录系统的应用程序逻辑。只是它应该处理的数据。它的应用程序逻辑驻留在我的模型层的应用层中。
价值对象可以在不存在于实体内部的情况下存在吗?
是的,他们可以。某些临时值对象只能在计算中使用,而永远不会持久化或与实体相关。
但是,这不是我们在这里谈论的对象类型。 LoginAuth
显然与User
有关系,并随之而来。
要确定这种关系的方向(你所说的"住在里面与住在外面"(,你必须考虑获得参考。在 DDD 中,只能从存储中解除冻结聚合根(通过存储库(。获得对根的引用后,可以导航到其聚合中的实体和值对象。
如果在这一点上遵循 DDD,则不允许直接从数据库中获取值对象。您必须加载整个聚合,然后通过实体获取对 VO 的引用。因此,您需要在实体中存储对值对象的引用,或者至少在实体中内置一些可以生成 VO 的内容。在您的示例中,这会导致User
持有LoginAuth
。反之亦然是行不通的。
注意:我的域不包含此登录的应用程序逻辑 系统。只是它应该处理的数据。应用程序逻辑 它驻留在我的模型层的应用层中。
如果您采用 CQRS 方法,并且"登录系统"仅意味着读取LoginAuth
,则可以绕过存储库并使用读取模型直接获取所需的数据。
但是,这仅涵盖阅读方面。我想您仍然希望能够在某个时候更改用户的登录身份验证,因此仍然需要为其进行写入端聚合突变。
首先要记住的是,当内部数据相等(而不是引用(时,Value Object 必须相等。
问题 1:如果您有两个LoginAuth
引用,它们保存不同的User
对象(具有相同的数据(,这将使两个LoginAuth
不相等。
2:如果有人改变了第一个用户引用的状态?但第二个用户引用仍然是一样的,就会发生大问题。你明白吗?
因为User
是一个必须具有id
的实体,所以LoginAuth
只能在里面保存id
值,而不是整个User
对象,然后你可以LoginAuth
放入你的数据库中或序列化,通过你的网络发送,任何你想要的。
值对象可以在不存在于实体内部的情况下存在吗?这样做是可以的,但你还没有尝试打破值对象的概念。
-------更新--------
也许这需要是一个实体? 没必要。您说您规范化了数据库并将LoginAuth
存储在单独的表中,假设login_auth
表,该表将User
的id
存储在第 user_id
列中,以确保单个User
只有一个LoginAuth
,user_id
作为主键(或唯一键(并在用于将LoginAuth
放入数据库的类中进行一些双重检查
没有实体,值对象可以存在吗?
是的,它们可以,因为值对象不应引用实体。
值对象没有标识,当两个值对象的属性相同时,它们相等。通常值对象用于描述货币,地址,权重类型。但LoginAuth
也可以是具有login
和password
属性的值对象。它还可以具有不同的其他属性。也许expirationDate
,lastLoginDate
或其他什么...
实体通常保存值对象。但是,如果要持久化聚合根,即使实体也无法生存。聚合根应引用实体或实体列表。
实体、值对象、聚合和根在域模型中具有自己的特定角色和用法。
我有一个值对象登录身份验证,其中包含用户身份验证 我的辅助登录系统的数据。
对于每个用户,选择辅助登录是可选的。所以 用户实体不保存登录身份验证值对象,而是保存 登录身份验证值对象包含它所属的用户实体。
域模型和数据库结构可能不同。
- 设计域模型时,您尝试表示目标域中存在的关系。
- 在设计数据库结构时,您更多地考虑数据的存储方式、规范化和非规范化。
域实体User
可以按以下方式设计,SecondaryLogin
可以是可选的:
public class LoginAuth
{
public string Login { get; private set; }
public string Password { get; private set; }
public LoginAuth(string login, string password)
{
Login = login;
Password = password;
}
}
public class User
{
public LoginAuth PrimaryLogin { get; private set; }
public LoginAuth SecondaryLogin { get; private set; }
public User(string login, string password)
{
PrimaryLogin = new LoginAuth(login, password)
}
public User(
string login,
string password,
string secondaryLogin,
string secondaryPassword) : this(login, password)
{
SecondaryLogin = new LoginAuth(secondaryLogin, secondaryPassword);
}
}
如果我们开始使用像实体框架或 NHibernate 这样的 ORM,默认情况下它们将生成下表。它们存储链接到实体的值对象以及实体:
Users
=====================================================
Id (PK) | int | not null |
PrimaryLogin_Login | nvarchar(255) | null |
PrimaryLogin_Password | nvarchar(255) | null |
SecondaryLogin_Login | nvarchar(255) | null |
SecondaryLogin_Password | nvarchar(255) | null |
在我的一个项目中,我想保持数据库规范化,并将属性的可选容器存储在单独的表中。
由于我的数据库已规范化,因此我将此值对象存储在 以user_id为主键的单独表(以确保 唯一性(。
如您所见,我的值对象不存在于实体内,而是 而是它自己,但它确实包含它所属的实体。
由于 NHibernate(Id 列是必需的,并且仅为实体生成表(,因此需要使其成为实体。但是,如果没有使用ORM并且实体是由您自己加载的,则可以根据需要存储它们。
ParentEntities
=====================================================
Id (PK) | int | not null |
Name | nvarchar(255) | not null |
OptionalEntities
=====================================================
Id (PK) | int | not null |
ParentEntityId (FK) | int | not null |
Login | nvarchar(255) | not null |
Password | nvarchar(255) | not null |
或者也可以建立不推荐的一对一关系,我不确定它是否适用于可选属性。
ParentEntities
=====================================================
Id (PK) | int | not null |
Name | nvarchar(255) | not null |
OptionalEntities
=====================================================
Id (PK) | int | not null |
Login | nvarchar(255) | not null |
Password | nvarchar(255) | not null |
在这种情况下,当数据与同一实体相关时,ParentEntities.Id
等于OptionalEntities.Id
。