Reputation: 3392
I'm just learning DDD (Eric Evans book is open in front of me) and I've come across a problem that I can't find an answer for. What do you do in DDD when you're just trying to get a simple list of lookup records?
Ex.
EmployeeID: 123
EmployeeName: John Doe
State: Alaska (drop-down)
County: Wasilla (drop-down -- will be filtered based on state).
For example, let's say that you have an Employee domain object, an IEmployeeRepository interface and an EmployeeRepository class. This will be used by a UI to show a list of employees and individual details. In the UI, you want to use a drop-down for the State and County where the employee lives. The Available counties will be filtered based on which state was chosen.
Unfortunately, the database tables and the UI look very different. In tblEmployees, it contains State Code=AK and County Code=02130, not the State and County Names.
The old way (before I began this DDD quest) would be pretty straightforward, just create 2 queries and use a DataReader to populate the drop-downs. Underneath the display in the drop-downs is the value, which gets automatically used in form posts.
With DDD, though, I'm not sure how you're supposed to do this. I first started by creating State and County objects as well repositories and interfaces for the repositories. However, writing 4 classes + 2 interfaces and the plumbing in the hbm.xml files + Employee Business objects seems like overkill for just 2 queries for 2 drop-downs. There has to be a better way, doesn't there? I'm not changing the records in the State or County tables any time soon and even if I did, it wouldn't be through this application. So I don't really want to create business objects for State and County if I don't have to.
The simplest solution that I see is to just create a helper class with methods that return dictionaries, such as GetStatesAll(), GetState() and GetCounties() and GetCounty(), but that just feels wrong from a DDD perspective.
Please help. How can I use DDD without overengineering just a couple of simple lookups?
Final Solution I think that I finally found my answer through experience, which was to put the GetStates() method into its own Data Access class, though not a repository class. Since I was only doing read-only access, I threw it into a struct DTO. Since the database was small, I threw them altogether into a single class, like Todd below described.
My conclusions:
Upvotes: 52
Views: 9140
Reputation: 5726
Well I read an article by Mathias Verraes some time ago talking about this here. He talks about Separating value objects in the model from concepts that serve the UI.
Quoting from the article when asked whether to model Countries as entities or value object:
There’s nothing intrinsically wrong with modelling countries as entities and storing them in the database. But in most cases, that overcomplicating things. Countries don’t change often. When a country’s name changes, it is in fact, for all practical purposes, a new country. If a country one day does not exist anymore, you can’t simply change all addresses, because possibly the country was split into two countries.
He suggested a different approach to introduce a new concept called AvailableCountry
:
These available countries can be entities in a database, records in a JSON, or even simply a hardcoded list in your code. (That depends on whether the business wants easy access to them through a UI.)
<?php
final class Country
{
private $countryCode;
public function __construct($countryCode)
{
$this->countryCode = $countryCode;
}
public function __toString()
{
return $this->countryCode;
}
}
final class AvailableCountry
{
private $country;
private $name;
public function __construct(Country $country, $name)
{
$this->country = $country;
$this->name = $name;
}
/** @return Country */
public function getCountry()
{
return $this->country;
}
public function getName()
{
return $this->name;
}
}
final class AvailableCountryRepository
{
/** @return AvailableCountry[] */
public function findAll()
{
return [
'BE' => new AvailableCountry(new Country('BE'), 'Belgium'),
'FR' => new AvailableCountry(new Country('FR'), 'France'),
//...
];
}
/** @return AvailableCountry */
public function findByCountry(Country $country)
{
return $this->findAll()[(string) $country];
}
}
So it seems there is a 3rd solution which is to model look up tables as both value objects and entities.
BTW make sure u check the comments section for some serious discussions about the article.
Upvotes: 4
Reputation: 12601
You may want to look into the concept of Command Query Separation. I wouldn't worry about typed repositories for lookup values, but I'd still probably use DTO type classes over datasets etc...
You may want to spend some time reading Greg Young's blogs starting from this one to the present. He doesn't talk about filling lookup data specifically but he often talks about not handling the reading/reporting functionality of your application through typed methods on a repository.
Upvotes: 9
Reputation: 17272
Using DDD I have something similar with the following:
interface IAddressService
{
IList<Country> GetCountries ();
IList<State> GetStatesByCountry (string country);
IList<City> GetCitiesByState (string state);
// snip
}
Country, State and City are value objects which come from a lookup table in the database.
Upvotes: 7
Reputation: 49251
You're reading the wrong book if you want to learn how to do DDD without over complicating it. :-)
The simplest solution you're proposing is fine if it meets your needs. Encapsulating address data in business objects can be as simple or as complicated as your application demands. For example, the State object has a one-to-many relationship with County so Employee really just needs to reference a County if you chose to model it that way. I would only introduce that sort of complexity if required.
Additionally, I don't think there's much to be gained by defining interfaces for your repositories unless there's a real possibility that you might have multiple repositories for your objects.
Upvotes: 3
Reputation:
The state and county are not entities, but value objects. They are not the subject of your system. The way you said you treated these previously is OK. When will you change the state or county records in your database, based on the changes in the state of your domain model? Not, so these will not need a repository.
Upvotes: 3