Minimize the framework

...

and allow yourself some DDD

"The Blue Book"

Clean Architecture Talk

Uncle Bob Martin

Text

... I'm a big fan

My Goals

  • Make the project tell its story from first glance
    • reflect this in the file structure as well
  • Decouple the domain logic from I/O
  • SOLID

Make the project tell its story from first glance

 

MVC Framework File Structure

  • Console
  • Controllers
    • Articles
    • Subscriptions
    • Comments
  • Helpers
    • DiscountCalculator
    • HeadingTrimmer
  • Models
    • Aricle
    • Subscription
    • Comment
  • Services
  • Views
  • generate-invoices.php

Decouple the domain logic from the I/O

Post::create()

public function create($request) { 
        // validate the data
        $this->validate($request, array(
                'title'         => 'required|max:255',
                'slug'          => 'required|alpha_dash|min:5|max:255|unique:posts,slug',
                'category_id'   => 'required|integer',
                'body'          => 'required'
            ));
        // store in the database
        $post = new Post;
        $post->title = $request->title;
        $post->slug = $request->slug;
        $post->category_id = $request->category_id;
        $post->body = Purifier::clean($request->body);
        if ($request->hasFile('featured_img')) {
          $image = $request->file('featured_img');
          $filename = time() . '.' . $image->getClientOriginalExtension();
          $location = public_path('images/' . $filename);
          Image::make($image)->resize(800, 400)->save($location);
          $post->image = $filename;
        }
        $post->save();
        $post->tags()->sync($request->tags, false);
        Session::flash('success', 'The blog post was successfully save!');
        return redirect()->route('posts.show', $post->id);
}

SOLID

We were going to:

  • Create a SPA
  • Make it live alongside WordPress
  • Use some data stored in WordPress posts in the SPA
  • Use the WordPress logged in user data

by

  • Creating a WP entry page for the SPA
  • Using WP's AJAX entry point
  • Loading the whole WP on every request
  • Masking it with adapter classes

Architecting

Started with the domain

  • Organized separate elements in packages
  • Introduced entry points - Use-Cases

Use Case

class SendInvoices {

    public function __construct(EntityManagerInterface $em, InvoiceSender $sender) {
        /* save dependecies */
    }

    public function execute(SendInvoicesParams $params) {
        /* stuff is happening here */

    }
}
class SendInvoicesParams {

    /* props, getters */

    private function __constructor() {}

    public static function fromArray(array $data) {
        /* build and validate */
    }
}

The UI

Not GUI...

HTTP UI

  1. Collect values from input variables into an array
  2. Create params for the use case needed
  3. Create the use case
  4. Execute the use case with params object
  5. Serialize return value
  6. Send to user

NO WAY TO REPEAT THIS EVERY TIME!!!

Generic configurable request handler

[
    'match' => [
        'method' => 'GET',
        'get.action' => 'list'
    ],
    'useCase' => [
        'className' => \Crawling\ListFoundProducts::class,
        'parametersMapping' => [
            'requestId' => 'get.crawlId',
            'search' => 'get.search',
            'page' => 'get.pageNumber',
            'sort' => 'get.sort'
        ],
        'serializer' => \UI\JsonSerializer::class
    ]
],
...

Then added some middleware to get the user id from WP

et voilà

The cake is not a lie

Photo by Daria Shevtsova from Pexels

...but what about the command line

[
    'match' => [
        'command' => 'invoices-send'
    ],
    'useCase' => [
        'className' => \Billing\SendInvoices::class,
        'parametersMapping' => [
            'testing' => '--testing::',
            'limit' => '--limit::'
        ],
        'serializer' => UI\ConsoleSerializer::class
    ]
],
...

It's absolutely the same!

A Recap

What we did was:

  • Create “entry” points in our domain in the form of command objects

  • Have the input to them in the form of value objects

  • Use middleware for non standard input

  • Map the relevant input from the environment to the “entry” points input

  • Execute the command

  • Serialize the return value and send it to the user

A Recap

What we achieved was:

  • A nicely layered architecture

  • A nice abstraction of the different inputs and outputs

  • Excellent separation of concerns between the domain logic and the infrastructure logic

  • Focus on the domain - the things that actually matter

  • SOLID code

  • First glance idea of the most important actions in out system

  • DDD

Action-Domain-Responder

Thank you

Made with Slides.com