Latest news about Bitcoin and all cryptocurrencies. Your daily crypto news habit.
I intentionally took a widely spread and simple domain that everyone, I guess, is familiar with. I want to demonstrate how it can be viewed differently, with object-thinking in mind. This post can be viewed as a sequence of steps that I take when modeling some domain.
First, let’s talk about forms
Forms nowadays are utilized as a tool for data validation with an intention to make something useful with this data. Quite often I see that validation resides aside from business-logic. In this case data is treated as being passive: first, validate it, then do this, and then do that. Well, it’s great, and it’s called Procedural programming. The next step is to move the validation logic, intrinsic to domain concepts, to those value-objects and entities. This is what DDD teaches us to do. In this case forms are just a tool for letting a user to mutate some data. But there are cases when form is a full-fledged domain concept, that just happens to look like web-form at some steps.
My domain
Verbal (ok, textual) descriptionIn the very beginning it’s useful to come up with a basic description of your application, of what it does, what is important, what is a domain it operates in, what are some real-life application examples. Here it is.
My domain is a registration of users. They open a page, see a registration form, fill it and then submit. Then confirmation email is sent on a email address, indicated while filling the form. It contains a confirmation link with a secret token, so if the user follows this link, it means that he filled an email that really belongs to him. So clicking this link completes the process of registration: a new user shows up in a system.
I’m not trying to model this domain in its entirety though. It’s just a sketch.
Semantic netI start decomposing with a semantic net. It helps me in discovering basic objects. Moreover, it visually represents what’s going on and how things are connected with each other. It’s quite trivial in this example.
Simple semantic net of user registration domain
Only nouns, no “services”While drawing a sketch, I keep in mind object metaphors. In practice it mainly results in the absence of service-classes. Why? Because of a David West’s human metaphor. Or Alan Kay’s cell metaphor. Because of what objects are all about in OOP. Objects are like smart and independent grown-ups which don’t need to be told how to do what they are expected to.
Understanding the domain rulesJust like in identifying service boundaries, I use the same technique. I plunge into the past to see how some particular domain looked like then.
So let’s consider some paper form. It could be some application form or registration form, just whatever. What it was like to register somewhere? You took the form. It was printed on a typewriter. You took a pen. Filled it. Then you gave it back to some kind of a secretary. He or she reviewed it, probably noted some typos or mistakes immediately, checked your passport to make sure you were who you said you were and than kept it for further validation that might require couple of more days. Then you could’ve come back several days later to find out how’s your form. Or you simply could receive a letter with results.
How to model a domain
A few words on how to model the domain rulesWhat’s different now, when there are no typewriters and no secretaries? The forms still need to be displayed and validated. Who should do that? Well, long story short, object thinking implies that a form itself should. Right now we’re modeling the behavior intrinsic to the process of registration. It has nothing to do with printers, any validators, other “er”s or “or”s, for now it’s just a registration form, a token and an email. OK, and a visitor with a user, which just happened to end with “or” and “er”. Nothing prevents these classes to utilize all they need to implement their responsibilities though. Moreover, it’s encouraged that objects own all the necessary resources: database connections, external resource, caching resources. If it’s too much for one class — no problem, decorators are the way to go. An example of this concept is an ORM: your objects cease being manipulated, they decide what to do themselves. And the same situation is all over the place. Services are inverted. They don’t operate upon objects anymore.So our entities’ responsibilities could look like the following. Registration form is responsible for displaying itself, validating and saving itself. Email is responsible for sending itself. To get a full picture we should come up with and elaborate a set of user stories.
User storiesWhich user stories constitute a process of registration? What about the following:
A visitor requests a form and fills it.
I can derive from this story that a form should have a responsibility to display itself.
A form makes sure that all fields are valid.
Validation process requires more elaboration. Remember what I wrote about how registration worked fifty years ago? A process of validation included that a secretary needed to check that you were who you said you were, so you showed him or her your passport. Today an email serves as a passport. We send a letter on an email that a visitor filled in that form, and he or she should confirm it, and this process is included in a term “validation”.So any registration inevitably includes an asynchronous part. There’s no sense in trying to validate everything synchronously then. What I leave in synchronous validation are some lightweight checks which when violated won’t ever let a registration to succeed. What are these checks? Well, for example that the birth date is less than today or a year ago. The birth date should seem to be real. Or that an email is RFC822-compliant. So if all such checks pass successfully, I say that the form can be accepted.Some asynchronous checks might be paralleled, some should be only sequential. Running ahead I can say that such checks could be implemented as saga. And each one of them requires its own user story. That’s how the whole process could be illustrated:
Form validation ending with registration of a userIf a form is valid, it registers a visitor (keep in mind that objects are active!), so it becomes a user.
So this story card unveils another form’s responsibility: user registration.
Next elaborated validation user stories could go. I don’t want to delve too deep here and confine myself with only those that concern email confirmation. Here they are:
If synchronous validation is ok, compose a confirmation email and send it to a visitor.A interaction diagram for composing and sending a confirmation email
Apparently, an email should be composed with a help of a form, after all it’s an information expert for it. But an email should possess all the resources to be able to send itself.
Visitor follows a confirmation link from an email and becomes a user.A interaction diagram for confirming an email address
Here a form delegates confirmation to the token since it knows better how to confirm an input token string. If it’s ok, then a form registers a new user. It’s a common flow, reflecting a domain when viewed with object-thinking in mind: one entity creates another, and so on. Entities (i.e., aggregates) don’t pop up themselves.
CRC cardsAlong with user stories discovering, I could create CRC cards. Even if talking about CRC cubes (a concept derived from CRC cards, I first encountered it in David West’s Object Thinking), the side that works best for me is the one with responsibilities. So I walk through all user stories involving a particular object and collect its responsibilities. They represent services that my object can provide, and they form an object’s contract. I won’t delve into formal representation of CRC cubes, let’s just move to the code.
Code
OK, so I look at my responsibilities and see that my form should be able to:
- display itself;
- validate itself;
- compose a confirmation email, as a part of validation;
- confirm an email, as part of validation as well;
- register a user.
So these are the candidates to form an object’s contract.
Keep in mind that the following code is just a representation of ideas and principles presented above. It’s more than a pseudo-code, but less than a production code.
Displaying a formI want my registration form to be responsible only for displaying data, leaving decoration to someone else. I don’t think that it’s a good idea to let the form be aware of every representation detail though, so I hope for a collaboration from specific classes. It might look like the following:
// an entry point. It might also be a controller action.public function display(){ (new RegistrationForm(new UUID(),new RegistrationFormDataStorage() )) ->display() ;}
And the form method itself. The implementation could be in this spirit.
class RegistrationForm{/** * @var $id UUID */private $id;/** * @var $storage DataStorage */private $storage;public function __construct(UUID $id, DataStorage $storage) { $this->id = $id; $this->storage = $storage; }public function display() { (new RegistrationWebForm(new HiddenElement( $this->id->value() ),new NameElement(),new PassportElement(),new EmailElement() )) ->display(); }}
Accepting a form
public function accept(array $data){try { (new RegistrationForm(new FixedUUID($data['id']),new RegistrationFormDataStorage() )) ->accept(new Name($data['name']),new Passport(new PassportNumber($data['passport_number']),new PassportIssuedAt($data['passport_issued_at']),new PassportIssuedWhere($data['passport_issued_where']) ),new Email($data['email']) ) ; } catch (Exception $e) { (new ErrorPage())->display(); } (new RegistrationAcceptedPage())->display();}
Again, it was how an entry point could look like. Here is implementation of RegistrationForm->accept() method:
public function accept( IValidatableElement $name, IValidatableElement $passport, IValidatableElement $email){ $this->storage->transactionally(function () use ($name, $passport, $email) { $id = $this->storage->save( ['name' => $name->value(),'passport' => $passport->value(),'email' => $email->value(), ] ) ; $this->storage->appendFormAcceptedEvent($id); } );}
Mind a Form accepted event that’s transactionally stored in a database. I don’t want to perform all logic in one run since I don’t want to abuse DDD’s “One aggregate — one transaction” rule of thumb. I have a separate handler that handles that event; it composes a confirmation email.
Compose a confirmation email
public function composeConfirmationEmail(UUID $formId){ (new RegistrationForm( $formId,new RegistrationFormDataStorage() )) ->composeConfirmationEmail(new SmtpClient(),new EmailConfirmationEmailHeader(),new EmailConfirmationEmailBody(), (new ConfirmationTokens(new TokenStorage() )) ->add(new ConfirmationToken(new UUID(),new TokenStorage(),new UUID() // it's a token string ) ) ) ->send();}
Here is a variation of how an object can be persisted. I use a collection-like class ConfirmationTokens that can be used for adding and searching for tokens. And here is a pretty straightforwardRegistrationForm->composeConfirmationEmail() implementation:
public function composeConfirmationEmail( ICanSendEmails $transport, EmailHeader $subject, EmailBody $body, ConfirmationToken $token){return(new ConfirmationEmail( $transport, $this->storage->getById($this->id)['email_address'], $subject, $body, $token )) ->save();}
And, finally, that’s an entry point for user confirming an email:
public function confirmEmail(UUID $formId, UUID $tokenValue){ (new RegistrationForm( $formId,new RegistrationFormDataStorage() )) ->confirmEmail( (new ConfirmationTokens(new TokenStorage() )) ->byValue($tokenValue) );}
Successful confirmation leads to a registration of a new full-fledged user:
public function confirmEmail(ConfirmationToken $token){if ($this->generatedToken()->confirm($token)) { $data = $this->storage->getById($this->id); (new User(new UUID(),new UserDataStorage() )) ->register(new Name($data['name']),new Passport(new PassportNumber($data['passport_number']),new PassportIssuedAt($data['passport_issued_at']),new PassportIssuedWhere($data['passport_issued_where']) ),new Email($data['email']) ); }}
Wrapping it up
So with fully discarding data-model and the way objects should be persisted, and concentrating exclusively on behavior, I can come up with a descent model.
How to Avoid Anemic Domain Model was originally published in Hacker Noon on Medium, where people are continuing the conversation by highlighting and responding to this story.
Disclaimer
The views and opinions expressed in this article are solely those of the authors and do not reflect the views of Bitcoin Insider. Every investment and trading move involves risk - this is especially true for cryptocurrencies given their volatility. We strongly advise our readers to conduct their own research when making a decision.