Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Proposal for service platform and repository pattern

Service Platform w/ Repository Pattern Proposal

This document will outline a proposal for a service platform (API) and a repository pattern as a business layer for IC.

Benefits

  • Have a data layer that is independent of models and controllers.
  • Have repo methods that can be consumed from other parts of the web application and from the command line.
  • Reduce logic in controllers and models, allowing controllers to focus on HTTP level work (request data, cookies, responses) and models to focus on creating relationships, casting columns, mutators, etc. The controllers should instead delegate work to the repos.
  • Repository methods can be reused by different parts of the web application and to perform work for an API request coming from outside (i.e. if we use a mobile platform like React Native it can call the API endpoints which in turn consume the repositories)
  • Have repo methods be easily unit tested as they will not be accessing session data, request data or external constants. They only work with what was passed to them through their method signature. Repo methods can however access a database, connect to APIs, or read from files. This is fundamental work for them as they are data layers as opposed to helper classes. Most of the time they will interface with a database, and it is recommended to inject database models through the repo's constructor so as to be easily mocked in a unit test.

Web App Route

Route::post('session', 'OrderController@create');

Service Platform Route (API)

Route::post('session', 'ApiOrderController@create');

Controller Example (Web App)

use App\Repos\OrderRepo;

class OrderController extends Controller {
    public function __contruct(OrderRepo $order) {
        $this->order = $order;
    }
    
    public function create(Request $request) {
        $order = $this->order->create($request->all(), Auth::id());
        
        return view('orders.thank-you', ['orderId' => $order->id]);
    }
}

Controller Example (API Service Call)

use App\Repos\OrderRepo;

class ApiOrderController extends Controller {
    public function __contruct(OrderRepo $order) {
        $this->order = $order;
    }
    
    public function create(Request $request) {
        $order = $this->order->create(
            $request->all(),
            $request->get('customer_id')
        );
        
        return response->json(compact('order'));
    }
}

Artisan

    $schedule->call(function() {
        $this->orderRepo->create([
            'delivery_method' => 'shipping'
            'total' => 9.99
        ], 123);
    })->cron('0 12 * * *');

Repo Example

namespace App\Repos;

use App\Models\Order;

class OrderRepo {
    public function __construct(Order $order) {
        $this->order = $order;
    }
    
    public function create(array $data, int $customerId) {
        $order = $this->order->create([
            'customer_id' => $customerId,
            'delivery_method' => $data['delivery_method'],
            'total' => $data['total'],
        ]);
        
        return $order;
    }
}

Testability

The repo methods above easily unit-tested.

class OrderRepositoryTest extends TestCase
{
    // using this trait will rollback out transactions so we don't actually write to the DB
    // another option is to mock the order model
    use DatabaseTransactions;
    /**
     * Test creating an order.
     *
     * @return void
     */
    public function testCreate()
    {
        // resolve an OrderRepo out of the IoC container with dependencies injected for us
        $orderRepo = resolve('App\Models\OrderRepo');
        
        $order = $orderRepo->create([
            'delivery_method' => 'pickup',
            'total' => 4.99,
        ], 123);

        $this->assertGreaterThan(0, $order->id);
    }
}

Summary

A key point of the repositories is they have no idea of what context they are being used in. As they shouldn't. They are just business methods that do some work and possibly returning a value to its caller, regardless if its in a web request or command line context.

If the next version of the mobile app uses Swift, React Native, etc. it will not have access to the same session as it does now. The current version of the mobile app uses a webview and the content is generated in Laravel, in the future it might not be the same. That's why the session var is being passed into the repo method through its signature, because we are considering that the mobile app may have its own session (or not use a session) so it passes the customerId in through its API request.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment