Prinsip KERING dengan Laravel

Pertimbangkan modul sederhana yang bertanggung jawab untuk menambah pengguna baru.

Dan dengan contohnya kita akan melihat kemungkinan penerapan prinsip KERING apa yang terbuka.

Bagi saya, prinsip KERING (Jangan Ulangi Diri Sendiri) selalu diwujudkan dalam dua definisi dasar:

  1. Duplikasi pengetahuan selalu merupakan pelanggaran prinsip
  2. Duplikasi kode tidak selalu merupakan pelanggaran prinsip

Mari kita mulai dengan pengontrol yang berisi jumlah minimum logika.

class UserController
{
    public function create(CreateRequest $request)
    {
        $user = User::create($request->all());
        
        return view('user.created', compact('user'));
    }
}

class UserApiController
{
    public function create(CreateRequest $request)
    {
        $user = User::create($request->all());
        
        return response()->noContent(201);
    }
}

Pada tahap awal, pengulangan kode seperti itu agaknya tidak berbahaya.
Tapi kami sudah memiliki duplikasi pengetahuan, dan duplikasi pengetahuan dilarang.
Untuk melakukan ini, kami menggeneralisasi pembuatan pengguna di kelas UserService

class UserService
{
    public function create(array $data): User
    {
        $user = new User;
        $user->email = $data['email'];
        $user->password = $data['password'];
        $user->save();

        return $user;
    }
    
    public function delete($userId): bool 
    {
        $user = User::findOrFail($userId);
 
        return $user->delete();
    }    
}

Setelah memindahkan semua logika bekerja dengan model ke layanan, kami menyingkirkan duplikasinya di controller. Tapi kami punya masalah lain. Katakanlah kita harus mempersulit proses menciptakan pengguna sedikit.

class UserService
{
    protected $blogService;
    
    public function __construct(BlogService $blogService)
    {
        $this->blogService = $blogService;
    }

    public function create(array $data): User
    {
        $user = new User;
        $user->email = $data['email'];
        $user->password = $data['password'];
        $user->save();
        
        $blog = $this->blogService->create();
        $user->blogs()->attach($blog);

        return $user;
    }
    
    //Other methods
}

Secara bertahap, kelas UserService akan mulai tumbuh dan kami berisiko mendapatkan kelas super dengan sejumlah besar dependensi.

CreateUser Single Action Class


Untuk menghindari konsekuensi seperti itu, Anda dapat memecah layanan ke dalam kelas tindakan tunggal.

Persyaratan dasar untuk kelas ini:

  • Nama yang mewakili tindakan yang akan dilakukan
  • Memiliki metode publik tunggal (saya akan menggunakan metode sihir __invoke )
  • Memiliki semua dependensi yang diperlukan
  • Memastikan kepatuhan dengan semua aturan bisnis di dalam dirinya sendiri, menghasilkan pengecualian ketika mereka dilanggar

class CreateUser
{
    protected $blogService;

    public function __construct(BlogService $blogService)
    {
        $this->blogService = $blogService;
    }

    public function __invoke(array $data): User
    {
        $email = $data['email'];
        
        if (User::whereEmail($email)->first()) {
            throw new EmailNotUniqueException("$email should be unique!");
        }
        
        $user = new User;
        $user->email = $data['email'];
        $user->password = $data['password'];
        $user->save();

        $blog = $this->blogService->create();
        $user->blogs()->attach($blog);

        return $user;
    }
}

Kami sudah memiliki cek bidang email di kelas CreateRequet, tetapi masuk akal juga untuk menambahkan centang di sini. Ini lebih akurat mencerminkan logika bisnis untuk menciptakan pengguna, dan juga menyederhanakan debugging.

Pengendali mengambil formulir berikut

class UserController
{
    public function create(CreateRequest $request, CreateUser $createUser)
    {
        $user = $createUser($request->all());

        return view('user.created', compact('user'));
    }
}

class UserApiController
{
    public function create(CreateRequest $request, CreateUser $createUser)
    {
        $user = $createUser($request->all());

        return response()->noContent(201);
    }
}

Akibatnya, kami memiliki logika yang sepenuhnya terisolasi untuk membuat pengguna. Lebih mudah untuk memodifikasi dan memperluas.

Sekarang mari kita lihat apa keuntungan yang diberikan pendekatan ini kepada kita.

Misalnya, ada tugas untuk mengimpor pengguna.

class ImportUser
{
    protected $createUser;
    
    public function __construct(CreateUser $createUser)
    {
        $this->createUser = $createUser;
    }
    
    public function handle(array $rows): Collection
    {
        return collect($rows)->map(function (array $row) {
            try {
                return $this->createUser($row);
            } catch (EmailNotUniqueException $e) {
                // Deal with duplicate users
            }
        });
    }
}

Kami mendapat kesempatan untuk menggunakan kembali kode dengan menyematkannya dalam metode Collection :: map (). Dan juga proses untuk pengguna kebutuhan kami yang alamat emailnya tidak unik.

Berpakaian


Misalkan kita perlu mendaftarkan setiap pengguna baru dalam sebuah file.

Untuk melakukan ini, kami tidak akan menanamkan tindakan ini di kelas CreateUser itu sendiri, tetapi menggunakan pola dekorator.

class LogCreateUser extends CreateUser 
{
    public function __invoke(array $data)
    {
        Log::info("A new user has registered: " . $data['email']);
        
        parent::__invoke($data);
    }
}

Kemudian, menggunakan wadah Laravel IoC , kita dapat mengaitkan kelas LogCreateUser dengan kelas CreateUser , dan yang pertama akan diimplementasikan setiap kali kita membutuhkan turunan dari yang kedua.

class AppServiceProvider extends ServiceProvider
{

    // ...

    public function register()
    {
        $this->app->bind(CreateUser::class, LogCreateUser::class);
    }

Kami juga memiliki kesempatan untuk membuat pengaturan pembuatan pengguna menggunakan variabel dalam file konfigurasi.

class AppServiceProvider extends ServiceProvider
{

    // ...

    public function register()
    {
         if (config("users.log_registration")) {
             $this->app->bind(CreateUser::class, LogCreateUser::class);
         }
    }

Kesimpulan


Ini adalah contoh sederhana. Manfaat nyata mulai muncul ketika kompleksitas mulai tumbuh. Kita selalu tahu bahwa kode itu ada di satu tempat dan batas-batasnya didefinisikan dengan jelas.

Kami mendapatkan keuntungan sebagai berikut: mencegah duplikasi, menyederhanakan pengujian
dan membuka jalan bagi penerapan prinsip dan pola desain lainnya.

All Articles