Categories
Dev

Laravel Domain Driven: Application Layer

An application’s goal is to get some kind of input, transform it for use in the domain layer, and represent the output to the user or store it somewhere.

Application Layer

  1. Purpose of Application Layer
    1. An application’s goal is to get some kind of input, transform it for use in the domain layer, and represent the output to the user or store it somewhere.
  2. What belongs to Application Layer?
    1. One project can have multiple Applications. (Such as every Laravel App has an HTTP and Console, they are standalone apps).
      1. any 3rd party integration
      2. RESTful API
      3. Client portal page
      4. Admin management site
    2. One Application can use mutiple Domains.
    3. Belongings:
      1. Controllers
      2. Requests
      3. Request-specific validation rulesl
      4. Middlewares
      5. Resources (blade, JS, css etc.)
      6. ViewModels
      7. HTTP QueryBuilders (URL query parser)
  3. How do we group code over in Application Layer?
    1. Structure of HTTP Application
    2. Other general purpose class put into Support folder. Thinks like base request, commonly used middlewares etc.

View Models

  1. View Models are model classes to view files.
    1. They are responsible for providing data to a view.
    2. They are simple classes that take data and transform it into something usable for the view layer.
    3. Critically, they have one responsibility and one responsibility only: providing the view with the correct data.
  2. Key features of View Models:
    // Define a view model
    **// Implement Arrayable can use ViewModel in Blade directly.
    // Implement Responsable can allow ViewModel return as a JSON data**
    class PostFormViewModel implements Arrayable, Responsable
    {
    		public function __construct(User $user, Post $post = null)
    		{
    				$this->user = $user;
    				$this->post = $post;
    		}
    
    		public function post(): Post
    		{
    				return $this->post ?? new Post();
    		}
    
    		public function categories(): Collection
    		{
    				return Category::allowedForUser($this->user)->get();
    		}
    }
    
    // Usage in Controller
    class PostController
    {
    		public function create()
    		{
    				$viewModel = new PostFormViewModel(
    						current_user()
    				);
    
    				return view('blog.form', compact('viewModel'));
    		{
    		
    		public function edit(Post $post)
    		{
    				$viewModel = new PostFormViewModel(
    						current_user(),
    						$post
    				);
    		
    				return view('blog.form', compact('viewModel'));
    		}
    }
    
    // Usage in Blade
    <input value="{{ $viewModel->post()->title }}" />
    <input value="{{ $viewModel->post()->body }}" />
    <select>
    		@foreach($viewModel->categories() as $category)
    		<option value="{{ $category->id }}">
    				{{ $category->name }}
    		</option>
    		@endforeach
    </select>
    
    1. All dependencies are injected which give the most flexibility to the out side context.
    2. The view mode expose some methods that can be used by the view.
    3. There will either be a new or existing post provided by the post method, depending on whether you are creating or editing a post.
    4. It is scalable when you want to add tag() to Post view model.
  3. Laravel Resource a ViewModel looks quite similar. But, the resource map one-to-one on a model. However, ViewModel can provide what ever data the view wants. Sometimes, they can be combined:
    class PostViewModel
    {
    		public function values(): array
    		{
    				return PostResource::make(
    						$this->post ?? new Post()
    				)->resolve()
    	)
    
  4. View Composer https://laravel.com/docs/8.x/views#view-composers
    1. The problem for View Composer
      // Register
      class ViewComposerServiceProvider extends ServiceProvider
      {
      		public function boot()
      		{
      				View::composer('profile', ProfileComposer::class);
      				View::composer('dashboard', function ($view) {
      				// …
      				});
      		}
      		// …
      }
      
      // Usage
      class ProfileController
      {
      		public function index()
      		{
      				return view('profile');
      		}
      }
      
      1. View composers are callbacks or class methods that are called when a view is rendered. If you have data that you want to be bound to a view each time that view is rendered, a view composer can help you organize that logic into a single location.
      2. You have no idea what view('profile') means when you use it. When your project is small, it is OK. But when you have hundreds of controllers, thousands of views, you will get lost.
      3. View Model, however, it makes clear fro the controller itself what variables are available to the view. And also you can explicitly pass data into View Model.

HTTP Queries

  1. Instead of passing Request $request into every controller function (or at least index() function), it would be better to pass XxxxQuery into function, so that, all Request Mapping, and things like applying filters, applying sortings, can be handled in XxxxQuery class.
    class QueryBuilderServiceProvider extends ServiceProvider
    {
        public function boot()
        {
            if ($this->app->runningInConsole() && function_exists('config_path')) {
                $this->publishes([
                    __DIR__.'/../config/query-builder.php' => config_path('query-builder.php'),
                ], 'config');
            }
    
            $this->mergeConfigFrom(__DIR__.'/../config/query-builder.php', 'query-builder');
        }
    
        public function register()
        {
            $this->app->bind(QueryBuilderRequest::class, function ($app) {
                return QueryBuilderRequest::fromRequest($app['request']);
            });
        }
    
        public function provides()
        {
            return [
                QueryBuilderRequest::class,
            ];
        }
    }
    
    // Usage in Controller
    class BookingController
    {
    		public function index(BookingIndexQuery $query)
    		{
    				**// Not just get() $query, but also can attach extra query
    				// (Not recommend though)**
    				$bookings = $query
    					->wherePaid()
    					->get();
    		}
    }
    
    **// BookingIndexQuery will handle:
    //  1. join table
    //  2. apply filter
    //  3. apply sorting
    //  etc ...
    // By extending QueryBuilder. 
    // (please see how we do QueryBuilder in previous discussions)**
    class BookingIndexQuery extends QueryBuilder
    {
    		$query = Booking::query()
    				->join('invoices', 'invoices.id', '=', 'booking.invoice_id');
    
    		parent::__construct($query, $request);
    
    		$this->applyFilters(
    				SearchFilter('paid')
    		);
    }
    
    1. The major idea behind it are:
      1. Bind incoming Requeset with QueryBuilderRequest, then map the query builder to Model fields.
      2. Apply those filters, query conditions, joins, order by, etc, outside of Controller.
      3. Dependency Injections makes more flexibility and reusability.

Jobs

  1. The Laravel docs clearly describes jobs as the place to be when you’re trying to execute asynchronous business logic.
  2. Jobs have lots of similarities to controllers:
    1. They receive input: controllers from the request and jobs specified by the developer (most of the time serialised).
    2. They are dispatched to process that input: controllers during an HTTP request, jobs mostly on an asynchronous queue.
    3. They both support middleware to add functionality before and after the actual processing.
    4. And finally their end result is some kind of output: controllers via HTTP responses, jobs by writing something to the database, sending mails, and of course, much more.
  3. Check this out: spatie/laravel-queueable-action.
    1. The main idea behind is to dispatch all actions in one job.
    2. Unlike other Spatie laravel packages, I don’t like this one. Because:
      1. You lose your meaningful naming and type (class) for Jobs. As well, not good for debug and logging.
      2. You can’t queue different jobs into different queues.
    3. That’s why unlike other repos, having thousands of stars, this one only has less than 300 stars.

Footnotes

  1. Strong Type Systems:
    1. Gary Bernhardt’s thoughts on type systems https://www.destroyallsoftware.com/talks/ideology
  2. DTOs:
    1. Matthias Noback on the difference between value objects and DTOs https://github.com/spatie/data-transfer-object/issues/17 Discuss about what’s the difference between Value Object and DTO.
  3. OOP:
    1. Alan Kay’s vision of OOP https://www.youtube.com/watch?v=oKg1hTOQXoY Alan is the inventor of OOP, this video explains his original vision of OOP.
    2. Sandi Metz’s vision of truly OO code https://www.youtube.com/watch?v=29MAL8pJImQ What is truely OOP code. How to get rid of if-else statement.
  4. Refactor normal controller “action” functions into real Actions:
    1. Refactoring to actions by Freek Van der Herten https://freek.dev/1371-refactoring-to-actions
  5. Transaction Script:
    1. Martin Fowler on transaction scripts https://martinfowler.com/eaaCatalog/transactionScript.html Enterprise Architecture about Transaction Script
  6. Anemic Domain Models:
    1. Martin Fowler on anemic domain models https://martinfowler.com/bliki/AnemicDomainModel.html
  7. Eloquent Collection:
    1. Custom eloquent collections by Tim MacDonald https://timacdonald.me/giving-collections-a-voice/ how to using custom Eloquent Collections
  8. Query Builders:
    1. Dedicated query builders by Tim MacDonald https://timacdonald.me/dedicated-eloquent-model-query-builders more in-depth about Query Builders
  9. State Machine:
    1. Christopher Okhravi explains state machines in depth https://www.youtube.com/watch?v=N12L5D78MAA A thourough explain about State Pattern
    2. Symfony’s workflow package to build complex state machines https://symfony.com/doc/current/workflow/workflow-and-state-machine.html
  10. Model Factory:
    1. Tighten’s approach to model factories https://tighten.co/blog/tidy-up-your-tests-with-class-based-model-factories A similar approach to model factory