Can value objects exist without Entities?
Asked Answered
F

3

5

I have a value object LoginAuth which contains the User authentication data for my secondary login system.

For every User it is optional to opt for the secondary login. So the User entity does not hold the LoginAuth value object but instead, the LoginAuth value object contains the User entity which it belongs to.

Since my database is normalized, I store this value object in a separate table where the user_id is the primary key (to ensure uniqueness).

As you can see, my value object doesn't live inside an entity but rather on it's own, but it does contain the entity it belongs to. My question is:

  • Can value objects exist without living inside entities?

  • Perhaps this would need to be an entity?

    Each LoginAuth is supposed to be unique (only a unique User is allowed per LoginAuth), so there won't be any equals of this VO.

Note: My domain does not contain the application logic of this login system. Just the data it's supposed to handle. The application logic of it resides in the application layer of my model layer.

Forzando answered 22/8, 2014 at 15:16 Comment(0)
G
5

Can value objects exist without living inside entities?

Yes, they can. Some ephemeral Value Objects can be used in computations only and never persisted or related to an Entity.

However, this is not the type of object we're talking about here. LoginAuth obviously has a relationship to User and is persisted along with it.

To determine the direction of that relationship (what you're referring to as "living inside vs living outside"), you have to think in terms of obtaining references. In DDD, only Aggregate Roots can be rehydrated from storage (via Repositories). Once you have a reference to a Root, you can navigate to the Entities and Value Objects in its Aggregate.

If you follow DDD on that point, you aren't allowed to fetch a Value Object directly from the database. You have to load a whole aggregate, then obtain a reference to the VO through an Entity. Thus, you need to store a reference to the Value Object in an Entity, or at least have something built into the Entity that can produce the VO. In your example, this results in User holding the LoginAuth. The reverse alone wouldn't work.

Note: My domain does not contain the application logic of this login system. Just the data it's supposed to handle. The application logic of it resides in the application layer of my model layer.

If you take a CQRS approach to this and if the "login system" only implies reading the LoginAuth, you could bypass Repositories and use a Read Model to get the data you want directly.

However, this only covers the read side of things. I guess you would still want to be able to change a User's LoginAuth at some point and therefore still need a write-side Aggregate mutation for it.

Gaiseric answered 26/8, 2014 at 13:52 Comment(2)
That means that a User who has not opted for the secondary login will just have null in place of the property of this LoginAuth VO? And there is no other way to prevent entity having null VO in it's valid state?Forzando
Either that, or if you manage to make the needed data uniform across primary and secondary login, you could have an always-present Login VO in User, containing either primary or secondary login data. Or you could have a Login VO with an IsPrimary boolean and nullable SecondaryLoginData. The good thing with VO's is that you can easily make them immutable, which prevents inconsistencies from happening (such as IsPrimary = true and non-null SecondaryLoginData).Gaiseric
I
1

The first things you have to remember is that Value Object must be equal when data inside is equal (not the reference).

Problem 1: If you have two LoginAuth reference which hold different object of User (which has the same data) that would make two LoginAuth unequal.

Problem 2: if someone alter state of first user reference? but second user reference still be the same, big problem gonna be happen. Do you understand?

Because User is an entity that has to have id, so LoginAuth can only hold id value inside, not whole User object, then you able to put LoginAuth into your database or serialize, send across your network, whatever you want.

Can value objects exist without living inside Entities? It's ok to do that, but you haven't try to break the concept of value object.

------- UPDATE --------

Perhaps this would need to be an entity? It's not necessary. You said that you normalized database and store LoginAuth in separate table, let's say login_auth table, which store id of User in column user_id, to make sure a single User have only one LoginAuth, make user_id as primary key (or unique) and make some double check in class which you use to put LoginAuth into database

Ilocano answered 22/8, 2014 at 19:20 Comment(0)
D
1

Can value objects exist without Entities?

Yes, they can since Value Objects should not refer to an Entity.

Value Object has no identity and two value objects are equal when their attributes are identical. Usually Value Objects are used to describe Money, Address, Weight types. But LoginAuth can also be a Value Object with login and password attributes. It can also have and different other properties. Maybe expirationDate, lastLoginDate or something else...

Entity holds Value Objects usually. But even entity can't live without Aggregate Root if you want to persist it. The aggregate root should refer to the entity or to a list of entities.

Entities, Value Objects, Aggregates and Roots have their own specific role and usage in the Domain Model.

I have a value object LoginAuth which contains the User authentication data for my secondary login system.

For every User it is optional to opt for the secondary login. So the User entity does not hold the LoginAuth value object but instead, the LoginAuth value object contains the User entity which it belongs to.

Domain model and database structure might be different.

  • When you design Domain Model you try to represent relations that exist in the target domain.
  • When you design database structure you think more about how data is stored, normalization and denormalization.

Domain entity User can be designed the following way and SecondaryLogin can be optional:

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);
    }
}

If we start using ORM like Entity Framework or NHibernate they will generate the following table by default. They store Value Objects linked to an Entity together with it:

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     |

In one one of my projects I wanted to keep my database normalized and to store optional container for properties in separate table too.

Since my database is normalized, I store this value object in a separate table where the user_id is the primary key (to ensure uniqueness).

As you can see, my value object doesn't live inside an entity but rather on it's own, but it does contain the entity it belongs to.

Because of NHibernate (Id column is required and tables are generated for entities only) it was needed to make it an Entity. But if no ORM is used and entities are loaded by yourself, you can store them as you wish.

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 |

or it is also possible to make One-to-One relation that is not recommended and I'm not sure if it works for optional properties.

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 |

In this case ParentEntities.Id is equal to OptionalEntities.Id when data is related to the same entity.

Dime answered 26/8, 2014 at 22:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.