문제

As I asked a question about singular/plural model naming conventions yesterday, I encountered the concept of a Domain Model. As I understand it so far, a Domain Model is simply an object which represents a an entity in my application.

To use a very simple example, something like this:

class User {

    private $name;

    public setName() {}
    public getName() {}

}

Now, the question comes to mind, how do I populate this "Domain Model", either from some input source, of from a database or data source?

While reading about Domain Models, I came about the impression that there should be nothing more than a representation of the Domain concept in question inside of them. So now I will also need another class (DAO?) responsible for interacting with the data source, in this case a database table named "User". My DAO class will handle inserts, updates, deletes, and multiple fetches.

I came up with this approach to populating a User Domain Model from input, in this case post data, and then saving the record to the database, with a UserDAO class:

/**
 * Populating Domain Model from input, and saving to database
 */

/** Instantiate User Domain Model, and populate it from input */
$user = new User();
$user->setName($_POST['name']);

/** Creating a new database record from User Domain Model */
$userdao = new UserDAO($pdo);
$userdao->insert($user);

And here is how I anticipated interacting with the database when I need to fetch data, in this case multiple user records:

/**
 * Fetching data from database to populate User Domain Models in an array
 */

/** Instantiate new UserDAO object to interact with User table */
$users = new UserDAO($pdo);
$users->findAll();

$user_collection = [];

/** Loop UserDAO object set to populate user collection array */
foreach ($users as $user) {

    /** Create new User domain object from row, and add to collection array */
    $user = new User($user);
    $user_collection[$user->name()] = $user;

}

It seems like the only real benefit here is organization.

My current iteration essentially has a User class that takes on all of the responsibilities of the UserDAO class above, and returns arrays of data from the database directly, which I then use in my "Controllers"/"Presenters" and which trickle through to my (passive) Views.

What I'm wondering is:

  1. Am I on the right track?

  2. Where does input validation belong? I assume it must go in Domain Model, if I am correct in my assumptions so far?

  3. What is the benefit of using this technique, aside from helping to organize the basic concepts the application will rely and operate on? Why do I need this extra layer instead of operating directly on array results from the DB?

도움이 되었습니까?

해결책

The key here is to look at which classes should have which responsibilities, and specifically, which responsibilities are needed to make your domain function.

User Domain Object

Should be responsible for telling you about its state with respect to the useful business rules of your application (isAdmin(), isBanned(), isSuspended(), getWarnLevel()). Think of this object like an API bucket. What do you want to know about it? What useful information can it tell? Build an API that answers those questions. Be careful not to let the User tell you too much about OTHER objects in the system. That should not be its responsibility.

Cares about:

  • Telling you about itself
  • Managing its own state

Doesn't care about

  • Whether it gets persisted
  • How its made
  • Any other objects (unless it's an aggregate root)
  • Lots of other stuff

User Repository

A class responsible for allowing you to retrieve and persist fully formed, existing Users. Maybe they persist only in memory during the current request. Maybe they persist in cache. Maybe they persist in MySQL. It doesn't matter. All this thing does is allow you to get users, and persist them. That is its responsibility. It can, but doesn't have to, know about the persistence mechanism. It only needs to know how to use the persistence mechanism.

(findById($id), findByEmail($email), findBanned(), findByCriteria($Criteria) - good candidate for the strategy or specification pattern, save($User), delete($User)). Again, the key here is to build an API that satisfies the business rules of the domain. Do you have a need to find a user by email address? Then make that an explicit access point in the repository. If you don't have a need, then don't. How do you need to find Users? Answer that with an API.

Cares about

  • Giving you access to existing User objects based on any arbitrary criteria
  • Invoking the persistence mechanism you've supplied it

Doesn't care about

  • How exactly User objects are persisted
  • Making User objects
  • Lots of other stuff

User Factory

UserRepositories are for handling existing User objects, but how do you create them in the first place? With factories. These can be simple factories, that just make one type of user. Or they can be abstract factories, that make different types of users. This is up to you and what your systems needs are. Again, think in terms of what API is needed to satisfy the business rules of your domain space: (make($type, $data), makeAdmin($data), makeMember($data)). PHP 5.6's variadic operator syntax will make this WAY cleaner to work with btw.

Cares about

  • Making shiny new Users

Doesn't care about

  • The source of the data for making those shiny new Users
  • What you do with the User after it's made
  • Lots of other stuff

User Gateway/Mapper

This could be your actual persistence mechanism: it might interface directly with a relational database, make use of a Factory, and get used by the Repository. It does the actual DB fetching, and maps the data into a format that the Factory can digest. The factory shouldn't be responsible for this mapping because it shouldn't have any knowledge of the source format, only the format it needs to assemble the domain object. Thus the mapping responsibility lies with the Gateway/Mapper.

Cares about

  • Where the User is getting persisted to/retrieved from, and how
  • Translating that data from persistence to something a Factory wants
  • Probably cares about a Factory

Doesn't care about

  • The specific storage driver (e.g. MySQL, Postgres) - this is where PDO comes in
  • Making a new User object
  • Lots of other stuff

Now, admittedly this looks way more simple than it really is. How do you handle children of aggregates (e.g. the many Comments that belong to one Post)? At what stage do you give those to the Post? Do you even give them to it all the time, or only when you explicitly ask for it (e.g. via a callback?) These are tough questions that I don't have the answers to, and that are partially answered by your domain needs in the first place.

ORM is a tough problem to solve. Doctrine and Eloquent are excellent, but don't strictly follow the pattern above. That's ok though. The pattern above is a guideline, not a rule. It's goal is to focus on separation of concerns and focused responsibility. It may not be necessary for your application to have all of these layers.

Concerning validation, there are two kinds of validation:

  1. Form input validation

  2. Domain object validation

Form validation is usually best done by a form validator class and some rules specifically defined for a given form. Usually you can define those rules in whatever form builder class you have (e.g. Form::text('first_name', array('rules' => 'required|alpha')) ). The controller should take the form validator as a dependency, but should not do the actual validation itself.

Domain object assembly validation (e.g. protecting the integrity of your model) can live either in the domain object itself via setters, or it can live in the factory. This depends entirely on how you plan to construct domain objects.

It should be noted that you should have BOTH types of validation: form validation to validate input, and domain object validation to validate data integrity when you create the object.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top