Question about Repositories and their Save methods for domain objects
Asked Answered
K

2

11

I have a somewhat ridiculous question regarding DDD, Repository Patterns and ORM. In this example, I have 3 classes: Address, Company and Person. A Person is a member of a Company and has an Address. A Company also has an Address.

These classes reflect a database model. I removed any dependencies of my Models, so they are not tied to a particular ORM library such as NHibernate or LinqToSql. These dependencies are dealt with inside the Repositories.

Inside one of Repositories there is a SavePerson(Person person) method which inserts/updates a Person depending on whether it already exists in the database.

Since a Person object has a Company, I currently save/update the values of the Company property too when making that SavePerson call. I insert / update all of the Company's data - Name and Address - during this procedure.

However, I really have a hard time thinking of a case where a Company's data can change while dealing with a Person - I only want to be able to assign a Company to a Person, or to move a Person to another Company. I don't think I ever want to create a new Company alongside a new Person. So the SaveCompany calls introduce unnecessary database calls. When saving a Person I should just be able to update the CompanyId column.

But since the Person class has a Company property, I'm somewhat inclined to update / insert it with it. From a strict/pure point of view, the SavePerson method should save the entire Person.

What would the preferred way be? Just inserting/updating the CompanyId of the Company property when saving a Person or saving all of its data? Or would you create two distinct methods for both scenarios (What would you name them?)

Also, another question, I currently have distinct methods for saving a Person, an Address and a Company, so when I save a Company, I also call SaveAddress. Let's assume I use LinqToSql - this means that I don't insert/update the Company and the Address in the same Linq query. I guess there are 2 Select Calls (checking whether a company exists, checking whether an address exists). And then two Insert/Update calls for both. Even more if more compound model classes are introduced. Is there a way for LinqToSql to optimize these calls?

public class Address
{
    public int AddressId { get; set; }
    public string AddressLine1 { get; set; }
    public string AddressLine2 { get; set; }
    public string City { get; set; }
    public string PostalCode { get; set; }        
}

public class Company
{
    public int CompanyId { get; set; }
    public string Name { get; set; }
    public Address Address { get; set; }
}


public class Person
{
    public int PersonId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
    public Company Company { get; set; }
    public Address Address { get; set; }

}

Edit

Also see this follow up question. How are Value Objects stored in a Database?

Keeley answered 23/3, 2009 at 10:18 Comment(2)
No answer because I'm like you on this, but have you considered that if Person is your Aggregate, Company is an immutable value object in this case? Then your Repository wouldn't be concerned with saving properties of Company, just the association itself.Pretor
In addition, before mining Generic Repositories, you might read this article by Greg Young. codebetter.com/blogs/gregyoung/archive/2009/01/16/…Pretor
E
6

I myself have used the IRepository approach lately that Keith suggests. But, you should not be focusing on that pattern here. Instead, there are a few more pieces in the DDD playbook that can be applied here.

Use Value Objects for your Addresses

First, there is the concept of Value Objects (VO) you can apply here. In you case, it would be the Address. The difference between a Value Object and an Entity Object is that Entities have an identity; VOs do not. The VO's identity really is the sum of it's properties, not a unique identity. In the book Domain-Drive Design Quickly (it's also a free PDF download), he explains this very well by stating that an address is really just a point on Earth and does not need a separate SocialSecurity-like identity like a person. That point on Earth is the combination of the street, number, city, zip, and country. It can have latitude and longitude values, but still those are even VOs by definition because it's a combination of two points.

Use Services for combining your entities into a single entity to act upon.

Also, do not forget about the Services concept in the DDD playbook. In your example, that service would be:

public class PersonCompanyService
{
  void SavePersonCompany(IPersonCompany personCompany)
  {
    personRepository.SavePerson();
    // do some work for a new company, etc.
    companyRepository.SaveCompany();
  }
}

There is a need for a service when you have two entities that need both need a similar action to coordinate a combination of other actions. In your case, saving a Person() and creating a blank Company() at the same time.

ORMs usualyl require an identity, period.

Now, how would you go about saving the Address VO in the database? You would use an IAddressRepository obviously. But since most ORMs (i.e. LingToSql) require all objects have an Identity, here's the trick: Mark the identity as internal in your model, so it is not exposed outside of your Model layer. This is Steven Sanderson's own advice.

public class Address
{
  // make your identity internal
  [Column(IsPrimaryKey = true
    , IsDbGenerated = true
    , AutoSync = AutoSync.OnInsert)]
  internal int AddressID { get; set; }

  // everything else public
  [Column]
  public string StreetNumber { get; set; }
  [Column]
  public string Street { get; set; }
  [Column]
  public string City { get; set; }
  ...
}
Earhart answered 23/3, 2009 at 22:3 Comment(11)
Thanks, I will look into this and report back!Keeley
Ok a question, I don't use LinqToSql classes directly, I have my own domain models that are independent from LinqToSql. The Repositories map these models to LinqToSql models for saving and retrieval. Since the domain model 'Address' would not have an AddressId, how would you solve the data access?Keeley
I think I would have to save the values inside the Person/Company tables! Value Objects should be immutable according to all the DDD Gurus. They have no identity. So an Address has to be saved somewhere, there's a relationship between Person and Address, but no Identity to create a relationship...Keeley
According to my DDD Books: With Value Objects, when I want a Person to have a new Address, I would create a new Address object and specify the values with its constructor method. The old Address will be discarded, there's way to mutate an existing address.Keeley
Yes, Value Objects should be immutable. But, if you want to use an ORM you really don't have another choice - it has to have some type of identity. I really don't mine decorating my model with Linq attributes, as described above. They can be easily stripped if need be.Earhart
This may conflict with my IAddressRepository above, but in a pure model, the concept using ORMs with immutables VOs is to control the writing and retrieving of all VOs for the Aggregate Root during its creation and persistence to the DB. In other words, you'd always save the Address collections.Earhart
Back to ORMs, you would use the internal modifier I outlined above to give the Address() VO an internal identity. Which you would use this identity for your EntityRef<T> relationships within your model. This keeps the LinqToSql in check with the internal IDs, while not exposing it publicly.Earhart
If you want to keep the abstraction of LinqToSql separate from your Repositories (the ideal state of using an Infrastructure layer as stated by DDD gurus), then your code should expect to be writing all Addresses() from the Customer model, on each save. You'd have to set an identity down there.Earhart
From what I gathered, NHibernate implements Value Objects with Components and save them into the Aggregate Root, so if a Person has a Home Address and Work Address, there would be additional columns in the Person Table (HomeAddress_AddressLine1 etc.). LinqToSql doesn't really support Value Objects.Keeley
See this interview about LinqToSql and Value Objects: infoq.com/interviews/jimmy-nilsson-linqKeeley
Btw, I actually learned a lot during this investigation, thanks for your inputKeeley
S
0

From my recent experience of using the repository pattern I think you would benefit from using a generic repository, the now common IRepository of T. That way you wouldn't have to add repository methods like SavePerson(Person person). Instead you would have something like:

IRepository<Person> personRepository = new Repository<Person>();
Person realPerson = new Person();
personRepository.SaveOrUpdate(realPerson);

This method also lends itself well to Test Driven Development and Mocking.

I feel the questions about behavior in your description would be concerns for the Domain, maybe you should have an AddCompany method in your Person class and change the Company property to

public Company Company { get; private set; }

My point is; model the domain without worrying about the how data will be persisted to the database. This is a concern for the service that will be using your domain model.

Back to the Repository, have a look at this post for good explanation of IRepository over LinqToSql. Mike's blog has many other posts on Repositories. When you do come to choose an ORM I can recommend HHibernate over LinqToSql, the latter is now defunct and NHibernate has a great support community.

Salt answered 23/3, 2009 at 16:23 Comment(4)
I was working really hard to implement generic repositories until I read this post by Greg Young. Excellent points that hadn't occurred to me. codebetter.com/blogs/gregyoung/archive/2009/01/16/…Pretor
I haven't seen the point in generic repositories - they really don't make much sense in my app...Keeley
@Jlembke, a good post from Greg Young. IRepository is great for getting started but I can understand the need to control how the querying is handled. I like the idea of using composition with the generic repository used in the concrete instance. This may play well with the something like an IoCSalt
@Kitsune, out of interest, how are you building your repository? Can you show and example?Salt

© 2022 - 2024 — McMap. All rights reserved.