Example for a newsletter double opt-in implementation using CQRS
We provide a vagrant config and a database scheme to run this example. Just do the following:
- Execute
vagrant up
- Follow the instruction shown by the provisioner script at the end
- Go to http://pma.cqrs-newsletter.de
- Import file
env/NewsletterDatabase.sql
- Go to: http://www.cqrs-newsletter.de
- As user I can subscribe with my e-mail address so that I'll receive newsletters.
- As user I will receive an e-mail after subscription so that I can confirm my e-mail address.
- As user I can trigger a resend of the confirmation e-mail so that I can confirm my e-mail address.
- As user I can confirm my e-mail address so that I can complete the subscription.
- As user I will receive a welcome e-mail so that I am sure the subscription is completed.
What the application
needs to present to the user
:
- Show a subscription form
- Depending on the validity and the existence/confirmation status of the e-mail address:
- E-mail address is valid and not subscribed:
Send a confirmation e-mail to theuser
and show a "Subscription initialized" page. - E-Mail address is invalid:
Show the subscription form again with an error message. - E-Mail address is subscribed, but not confirmed: Show a "Resend e-mail" form with a hint message and a button to resend the cofirmation e-mail.
- E-Mail address is subscribed and confirmed:
Show the subscription form again with a hint message.
- E-mail address is valid and not subscribed:
- Show a confirmation form.
- Show a "Subscription confirmed" page.
Due to the cirumstances we will call the domain "newsletter". :)
Let's break the user interfaces down to GET and POST requests:
GET
: /newsletter/show-subscription-formPOST
: /newsletter/initialize-subscriptionGET
: /newsletter/show-subscription-initializedGET
: /newsletter/show-subscription-formGET
: /newsletter/show-resend-confirmation-form
(followed by aPOST
: /newsletter/resend-confirmation-mail and aGET
: /newsletter/show-subscription-initialized)GET
: /newsletter/show-subscription-form
GET
: /newsletter/show-confirmation-formPOST
: /newsletter/confirm-subscriptionGET
: /newsletter/show-subscription-confirmed
So we have 3 unique write
requests and 5 unique read
requests.
There are 3 main concerns:
- Writing subscriptions (Init/Confirm or Insert/Update in CRUD language)
- Reading subscriptions (Find)
- Sending e-mails to the user
These concerns lead to 3 service classes. Their interfaces could be described as follows:
<?php
interface NewsletterWriteServiceInterface
{
/**
* @param string $email
*
* @throws EmailAddressIsNotValid
* @throws SubscriptionAlreadyInitialized
* @throws SubscriptionAlreadyConfirmed
* @throws AddingSubscriptionFailed
*
* @return SubscriptionInterface
*/
public function initializeSubscription( $email );
/**
* @param SubscriptionId $subscriptionId
*
* @throws SubscriptionNotFound
* @throws SubscriptionAlreadyConfirmed
*
* @return SubscriptionInterface
*/
public function confirmSubscription( SubscriptionId $subscriptionId );
}
<?php
interface NewsletterReadServiceInterface
{
/**
* @param SubscriptionId $subscriptionId
*
* @throws SubscriptionNotFound
*
* @return SubscriptionInterface
*/
public function findSubscriptionById( SubscriptionId $subscriptionId );
/**
* @param string $email
*
* @throws SubscriptionNotFound
*
* @return SubscriptionInterface
*/
public function findSubscriptionByEmail( $email );
}
<?php
interface NewsletterMailServiceInterface
{
/**
* @param SubscriptionInterface $subscription
*
* @throws SendingConfirmationMailFailed
*/
public function sendConfirmationMail( SubscriptionInterface $subscription );
/**
* @param string $email
*
* @throws SubscriptionNotFound
* @throws SubscriptionAlreadyConfirmed
* @throws SendingConfirmationMailFailed
* @throws EmailAddressIsNotValid
*
* @return SubscriptionInterface
*/
public function resendConfirmationMail( $email );
/**
* @param SubscriptionInterface $subscription
*
* @throws SendingWelcomeMailFailed
*/
public function sendWelcomeMail( SubscriptionInterface $subscription );
}