Reputation: 7141
In the book Implementing DDD, there's mention of creating a TenantId
value object. This makes sense to me, as a GUID
could be empty which isn't a valid TenantId
, so by making a TenantId
value object I can protect against this (I also have other value objects like Name
, PhoneNumber
, EmailAddress
, etc):
public class TenantId
{
public TenantId(Guid id)
{
this.SetId(id);
}
public Guid Id { get; private set; }
private void SetId(Guid id)
{
if (id == Guid.Empty)
{
throw new InvalidOperationException("Id must not be an empty GUID.");
}
this.Id = id;
}
}
What I'm interested in, is should I, or should I not, use this TenantId
on a service method, something like:
TenantId tenantId = new TenantId(model.TenantId); // model.TenantId being a GUID.
this.tenantService.GetTenant(tenantId);
Or should I use the more raw form in the service method arguments:
this.tenantService.GetTenant(model.TenantId); // again model.TenantId is a GUID.
The book seems to sometimes do it one way and sometime another. What thoughts do people have on the pros and cons of these approaches?
Upvotes: 1
Views: 790
Reputation: 7283
I prefer using Custom Value Object as Aggregate's identity. I think it gives more clue when other developers want to invoke the api. Consider this:
Tenant findBy(String identity); //others may pass an String instance but not id and the compliler can't check it out for you
And you can't add this below method:
Tenant findBy(String name);//assuming name unique but it failed for compiling
Why use the raw type if you have already had a specific type?
Edit
I usually use custom type for repository and application service, for domain service, please refer to @Giacomo Tesio‘ answer. I think the "use the entity directly" makes more sense.
Upvotes: 1
Reputation: 7210
If the service doesn't require restricted access to the entity, pass the value object since it is a shared identifier, but I would code it something like this:
public class TenantId : IEquatable<TentantId>
{
public TenantId(Guid id)
{
if (id == Guid.Empty)
{
throw new InvalidOperationException("TenantId must not be an empty GUID.");
}
_id = id;
}
private readonly Guid _id;
public Equals(TentantId other)
{
if (null == other) return false;
return _id.Equals(other._id);
}
public GetHashcode()
{
return _id.GetHashcode();
}
public override Equals(object other)
{
return Equals(other as TenantId);
}
public ToString()
{
return _id.ToString();
}
}
However in some cases, a domain service should be invoked only with entities that the user can access in the repository: in those cases, the public interface of the domain service require the entity (even if the implementation just use the identifier).
Upvotes: 3