Real world implementation of

The Clean Architecture


Created by Jeroen De Dauw for Wikimedia Deutschland
Licensed CC BY-SA 3.0


entropywins.wtf

IvoryTower.jpg

We are hiring!

software.wikimedia.de


Real world implementation of

The Clean Architecture


Created by Jeroen De Dauw for Wikimedia Deutschland
Licensed CC BY-SA 3.0


entropywins.wtf

IvoryTower.jpg

They did it by making the single worst strategic mistake that any software company can make: [...] rewrite the code from scratch.

-- Joel Spolsky

Size: complexity     Color: maintainability

[...] the classic approach to dealing with software's essential complexity: low-dependency-architecture

-- Leading Lean Software Development

Source: CleanCoders.com

Source: CleanCoders.com


class CancelDonationUseCase {
    private /* DonationRepository */ $repository;
    private /* Mailer */ $mailer;

    public function cancelDonation( CancelDonationRequest $r ): CancelDonationResponse {
        $this->validateRequest( $r );

        $donation = $this->repository->getDonationById( $r->getDonationId() );
        $donation->cancel();
        $this->repository->storeDonation( $donation );

        $this->sendConfirmationEmail( $donation );

        return new CancelDonationResponse( /* ... */ );
    }
}
					

$app->post(
    '/cancel-donation',
    function( Request $httpRequest ) use ( $factory ) {
        $requestModel = new CancelDonationRequest(
            $httpRequest->request->get( 'donation_id' ),
            $httpRequest->request->get( 'update_token' )
        );

        $useCase = $factory->newCancelDonationUseCase();
        $responseModel = $useCase->cancelDonation( $requestModel );

        $presenter = $factory->newNukeLaunchingResultPresenter();
        return new Response( $presenter->present( $responseModel ) );
    }
);
					

Source: CleanCoders.com

Source: CleanCoders.com

Source: CleanCoders.com

Source: CleanCoders.com

Lessons learned                        

Validation & Use Case boundary                        









  1. Always wrong (donation amount < 0)
  2. Wrong for some usecases (donation amount > 10000)
  3. Wrong input format (donation amount is "0.4.2")

class UseCases\AddDonation\AddDonationRequest {
	private $donorType;
	private $donorFirstName;
	private $donorLastName;
	private $donorSalutation;
	private $donorTitle;
	private $donorCompany;
	private $donorStreetAddress;
	private $donorPostalCode;
	private $donorCity;
	private $donorCountryCode;
	private $donorEmailAddress;
	/* ... */
}					

class Domain\Donation {
    private /* int|null */            $id
    private /* DonationPayment */     $payment
    private /* Donor */               $donor
    /* ... */
}					

class Domain\DonationPayment {
    private /* Euro */                $amount
    private /* int */                 $intervalInMonths
    private /* PaymentMethod */       $paymentMethod
}
					

class UseCases\AddDonation\AddDonationRequest {
    private /* DonationPayment */     $payment
    private /* Donor */               $donor
    /* ... */
}
					

class AddDonationUseCase {
    public function addDonation( AddDonationRequest $request ) {
        $donation = $this->newDonationFromRequest( $request );

        $validationResult = $this->validator->validateDonation( $donation );

        // ...
    }

}
					

class AddDonationUseCase {
    public function addDonation( AddDonationRequest $request ) {
        $validationResult = $this->validateRequest( $request );

        // ...

        $donation = $this->newDonationFromRequest( $request );

        // ...
    }

}
					

Lessons learned                        

Bounded Contexts                                  










class Donation {
    private /* int|null */            $id
    private /* PersonalInfo|null */   $personalInfo
    /* ... */
}
					

class PersonalInfo {
    private /* PersonName */          $name
    private /* PhysicalAddress */     $address
    private /* string */              $emailAddress
}
					

class MembershipApplication {
    private /* int|null */            $id
    private /* PersonalInfo|null */   $personalInfo
    /* ... */
}
					

class PersonalInfo {
    private /* PersonName */           $name
    private /* PhysicalAddress */      $address
    private /* string */               $emailAddress
    private /* PhoneNumber|null */     $phone
    private /* DateTime|null */        $dateOfBirth
					

    private /* int|null */             $membershipNumber
    private /* PhysicalAddress|null */ $shippingAddress
    private /* string|null */          $shirtSize
					

class Donor {
    private /* PersonName */          $name
    private /* PhysicalAddress */     $address
    private /* string */              $emailAddress
}
					

class Applicant {
    private /* PersonName */          $name
    private /* PhysicalAddress */     $address
    private /* EmailAddress */        $email
    private /* PhoneNumber */         $phone
    private /* DateTime|null */       $dateOfBirth
}
					







Duplication is far cheaper than the wrong abstraction

-- Sandi Metz


bit.ly/fallacy-of-dry


class Donor {
    private /* PersonName */          $name
    private /* PhysicalAddress */     $address
    private /* string */              $emailAddress
}
					

class Applicant {
    private /* PersonName */          $name
    private /* PhysicalAddress */     $address
    private /* EmailAddress */        $email
    private /* PhoneNumber */         $phone
    private /* DateTime|null */       $dateOfBirth
}
					

Recommended material

The Clean Architecture (blog by Robert C Martin)

Architecture, Use Cases, and High Level Design (CleanCoders.com video)

Source code

github.com/wmde/FundraisingFrontend

Application in production

spenden.wikimedia.de

This presentation

entropywins.wtf

We are hiring!

software.wikimedia.de

Cattribution

Questions?