De S van SOLID | Eenvoud

De S van SOLID

26 februari 2021

Iedereen heeft zo zijn principes. Wij, de developers, hebben dat zeker ook. Vijf van die principes vormen samen SOLID. Maar wat houden die principes eigenlijk in?

SOLID is een acroniem voor de eerste vijf object-oriented design (OOD) principes, opgesteld door Robert C. Martin (Uncle Bob). Deze man kwamen we al eerder tegen bij ons artikel over ‘Clean Code‘.

SOLID is bedoeld om code meer onderhoudbaar te maken, beter te begrijpen en makkelijker uit te breiden. Ook dit zagen we ook al eerder bij ‘Clean Code’. Belangrijke zaken dus bij het ontwikkelen van software.

Laten we deze keer eens kijken naar het eerste principe; de S van Single Responsibility Principle

Single Responsibility Principle

Single Responsibility Principle: A class/function should be responsible for one and only one thing.

Of zoals Uncle Bob het graag uitlegt: Alles heeft zijn eigen plek en alles moet ook op zijn eigen plek staan.

De L van Laravel

Als voorbeeld neem ik een Controller in Laravel. Eentje die je vaak tegenkomt.

public function store(Request $request, User $user)
{
        // 1. validate data
        $data = $request->validate([
           'name'   => 'required',
            'email' => 'required|email',
        ]);

        // 2. save to database
        $user->name  = $request->name;
        $user->email = $request->email;
        $user->save();

        // 3. send JSON response
        return response()->json(['user' => $user], 201);
}

Een controller heeft eigenlijk maar 1 taak; regel ‘de flow’ van de applicatie. Het krijgt iets, doet er iets mee en geeft een response terug. Alle andere taken moet hij uitbesteden aan de applicatie zelf.

Hier weet de controller ook wat de velden van de input zijn, hoe hij dat moet valideren en wegschrijven. En wat en hoe hoe het weer moet teruggeven.

Al deze ‘responsibilities’’ moeten bij hem vandaan en hij moet zich alleen nog druk maken over ‘de flow’. Laten we ze een voor een bij hem weg halen.

1. validate data

Laravel heeft een mooie manier om te valideren; form requests. Form requests zijn request classes die validatie logica bevatten. Alles heeft zijn eigen plek.

class StoreUserRequest extends FormRequest
{
        public function authorize()
        {
            // 
        }

        public function rules()
        {
            return [
                'name'  => 'required',
                'email' => 'required|email',
            ];
        }
}

De manier om deze validatieregels te gebruiken is simpelweg de request uit de store functie in de controller te type-hinten met je zelfgemaakte Request.

public function store(StoreUserRequest $request, User $user)
{
        //
}

Het request wordt dan (geautoriseerd en) gevalideerd door Laravel voordat de controller methode wordt aangeroepen.

2. Opslaan van data

Ook voor het opslaan heeft Laravel een mooie oplossing; Repositories. Een Repository is eigenlijk niet iets specifieks voor Laravel, maar een design pattern dat kan worden toegepast in Laravel.

Repositories hebben de taak om zaken op te slaan. In ons geval is dat wegschrijven naar de database.

class UserRepository
{
        public function create($userData)
        {
            $user = new User();

            $user->name  = $request->name;
            $user->email = $request->email;
            $user->save();

            return $user;
        }
}

Hoe kunnen we deze gebruiken? Door het repository te type-hinten wordt deze automatisch geïnjecteerd door Laravel. Net als het User object in de oorspronkelijke controller.

public function store(StoreUserRequest $request, UserRepository $userRepository)
{
        $user = $userRepository->create($request);

        return response()->json(['user' => $user], 201);
}

3. JSON response

Ook voor de response heeft Laravel iets moois in huis; JsonResources.
Met resources heb je veel meer controle over de output van je response.

Stel we willen alleen de naam teruggeven in de response.

class UserResource extends JsonResource
{
        public function toArray($request)
        {
            return [
                'name' => $this->name,
            ];
        }
}

Het resultaat

De controller komt er dan als volgt uit te zien:

public function store(StoreUserRequest $request, UserRepository $userRepository)
{
        $user = $userRepository->create($request);

        return new UserResource($user);
}

De nieuwe controller doet nu alleen de hoofdtaken. Hij krijgt data binnen. Maakt een user aan en geeft deze weer terug in een response.

De details laat hij over aan de verschillende onderdelen van de applicatie die daar verantwoordelijk voor zijn.

Als de structuur van de User aangepast wordt, heeft de controller daar geen last meer van. Dat in tegenstelling tot de eerste versie van de controller.

De J van Ja maar

Sommige ontwikkelaars vinden dat ‘Single Responsibility Principle’ niet staat voor dat een class maar één verantwoordelijkheid mag hebben. Zij vinden dat er maar één class verantwoordelijk mag zijn voor een bepaalde taak. Dat kan bijvoorbeeld opslaan van data zijn of het versturen van een response.

Het voorbeeld hierboven geeft wel mooi aan dat dat hand in hand samen gaat. In ons voorbeeld is de verantwoordelijkheid van het opslaan bij het repository komen te liggen en bij de controller weggehaald. Als we er voor zorgen dat het repository ook de enige is die dat kan doen, voldoen we ook aan de andere definitie. Zo ook bij de validatie.

In plaats van steggelen over definities, kunnen ontwikkelaars zich beter druk maken over de achterliggende gedachtes. Ofwel de S van ‘seur niet’.