Réteges architektúra clean code backend fejlesztésben

Réteges architektúra anélkül, hogy belefulladnánk a túltervezésbe

Sziasztok! Ma arról szeretnék beszélni, hogyan lehet egy backend alkalmazásunkat rétegesen felépíteni anélkül, hogy a tervezési fázisban elveszítenénk a lendületet. Gyakran látom, hogy a fejlesztők két véglet között ingadoznak: vagy összevissza kódolnak mindent egy fájlba, vagy olyan komplex architecture-t akarnak megvalósítani, hogy a projekt elején több időt töltünk UML diagramokkal, mint tényleges kódolással. A titok a középutas megközelítésben rejlik: építsünk strukturáltan, de flexibilisen.

Miért éri meg rétegezni?

Először is, tisztázzuk, miért is beszélünk réteges architektúráról. Alapvetően a clean code egyik alapköve a szeparáció: külön réteg kezelje az adatbázis kommunikációt, külön az üzleti logikát, és külön a prezentációt. Ha ezeket keverjük, pár hónap múlva már senki sem fogja érteni, hogy hol van az a bizonyos validator, ami elvileg „valahol a UserManager class-ban lehet, de néha a Controller is csinál belőle”.

A lényeg nem az, hogy mindenáron követni kelljen egy sablont, hanem hogy logikailag elkülönültek legyenek a felelősségi körök. Ez teszi karbantarthatóvá, tesztelhetővé és bővíthetővé a kódot.

Egy gyakorlati példa: egyszerű felhasználókezelés

Képzeljük el, hogy egy közepes összetettségű PHP alkalmazást fejlesztünk. Nincs szükségünk egyből öt rétegre és tucatnyi interfészre. Kezdjük három alapréteggel:

1. Adatréteg (Repository/DAL): Itt történik az adatbáziskapcsolat és a nyers adatok kezelése.
2. Üzleti réteg (Service): Itt lakik az alkalmazás „agyá”, a szabályok, számítások, validációk.
3. Prezentációs réteg (Controller): Ez kapja a HTTP kéréseket és adja vissza a választ (legyen az JSON vagy egy renderelt nézet).

Nézzük meg ezt egy konkrét, leegyszerűsített példán keresztül. Tegyük fel, hogy új felhasználót kell regisztrálnunk.

1. Adatréteg (Repository)
Ez a réteg a technológiafüggő dolgokért felelős: PDO, SQL, stb. Az egyetlen célja, hogy adatot kérjen vagy mentsen.

// UserRepository.php
class UserRepository {
    private $db;
    
    public function __construct(PDO $db) {
        $this->db = $db;
    }
    
    public function findByEmail(string $email): ?array {
        $stmt = $this->db->prepare("SELECT * FROM users WHERE email = :email");
        $stmt->execute(['email' => $email]);
        return $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
    }
    
    public function createUser(array $userData): int {
        $stmt = $this->db->prepare("INSERT INTO users (email, name, password_hash) VALUES (:email, :name, :password_hash)");
        $stmt->execute($userData);
        return $this->db->lastInsertId();
    }
}

2. Üzleti réteg (Service)
Itt történik a valódi munka. A Service nem tud és nem is akar SQL-t írni. Csak a Repository-ra támaszkodik, és alkalmazza az üzleti szabályokat.

// RegistrationService.php
class RegistrationService {
    private $userRepository;
    
    public function __construct(UserRepository $userRepository) {
        $this->userRepository = $userRepository;
    }
    
    public function registerNewUser(string $email, string $name, string $plainPassword): array {
        // 1. Validáció (ügyféloldali validáció mellett is!)
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException('Érvénytelen email cím.');
        }
        
        // 2. Üzleti logika: létezik-e már a user?
        if ($this->userRepository->findByEmail($email)) {
            throw new RuntimeException('Ez az email cím már regisztrálva van.');
        }
        
        // 3. Adat előkészítése az adatréteg számára
        $passwordHash = password_hash($plainPassword, PASSWORD_DEFAULT);
        $userData = [
            'email' => $email,
            'name' => $name,
            'password_hash' => $passwordHash
        ];
        
        // 4. Adat mentése a Repository-n keresztül
        $newUserId = $this->userRepository->createUser($userData);
        
        // 5. Visszaadni, amit a Controller-nek vagy más Service-nek szüksége lehet
        return ['user_id' => $newUserId, 'email' => $email, 'name' => $name];
    }
}

3. Prezentációs réteg (Controller)
A Controller feladata a HTTP kérés „kiszolgálása”: adatok kinyerése a kérésből, a Service meghívása és a megfelelő válasz (sikeres JSON vagy hiba) összeállítása.

// RegistrationController.php
class RegistrationController {
    private $registrationService;
    
    public function __construct(RegistrationService $registrationService) {
        $this->registrationService = $registrationService;
    }
    
    public function handleRequest() {
        header('Content-Type: application/json');
        
        try {
            $email = $_POST['email'] ?? '';
            $name = $_POST['name'] ?? '';
            $password = $_POST['password'] ?? '';
            
            // A Controller NEM validál, NEM számol jelszóhashet. Csak továbbít a Service-nek.
            $result = $this->registrationService->registerNewUser($email, $name, $password);
            
            echo json_encode(['success' => true, 'data' => $result]);
            
        } catch (InvalidArgumentException $e) {
            http_response_code(400);
            echo json_encode(['success' => false, 'error' => $e->getMessage()]);
        } catch (RuntimeException $e) {
            http_response_code(409); // Conflict
            echo json_encode(['success' => false, 'error' => $e->getMessage()]);
        } catch (Exception $e) {
            http_response_code(500);
            echo json_encode(['success' => false, 'error' => 'Belső szerverhiba.']);
        }
    }
}

A frontend: tisztán és egyszerűen

A backend ilyen struktúrája tökéletesen összeillik egy átlátható frontenddel. Használjunk például egy kis jQuery-t a form elküldéséhez, Bootstrap-t a gyors, kellemes kinézethez, és saját SCSS-t a testreszabáshoz.

// register.js - A jQuery kód tömör és kifejező
$(document).ready(function() {
    $('#registrationForm').on('submit', function(e) {
        e.preventDefault();
        
        var formData = $(this).serialize();
        
        $.ajax({
            url: '/api/register.php',
            method: 'POST',
            data: formData,
            dataType: 'json'
        })
        .done(function(response) {
            if (response.success) {
                // Bootstrap modal vagy alert a sikerért
                $('#successAlert').removeClass('d-none').text('Sikeres regisztráció!');
                $('#registrationForm')[0].reset();
            } else {
                $('#errorAlert').removeClass('d-none').text(response.error);
            }
        })
        .fail(function() {
            $('#errorAlert').removeClass('d-none').text('Hálózati hiba történt.');
        });
    });
});
// _registration.scss - SCSS modul a stílushoz
#registrationForm {
    max-width: 500px;
    margin: 2rem auto;
    
    .alert {
        // SCSS beágyazással
        &.d-none {
            display: none;
        }
    }
    
    .form-group {
        margin-bottom: 1.5rem;
        
        label {
            font-weight: 600;
            margin-bottom: 0.5rem;
            color: #333; // Változóval is lehetne
        }
    }
}

Gyakori buktatók és tanácsok

1. A rétegek átjárhatatlanok legyenek: A Controller soha ne hívjon meg közvetlenül a Repository-t, csak a Service-en keresztül. Ez tartja fenn a felelősség szétválasztást.
2. Ne tervezz előre mindent: Nem kell minden Service-nek egyből Interface-t készíteni. Csak akkor vezess be absztrakciót, ha valóban két különböző implementációra lesz szükség (pl. tesztelés miatt, vagy mert később cserélni akarod az adatbázist).
3. A rétegek kommunikáljanak egyszerű adatszerkezetekkel: Használj asszociatív tömböket, vagy egyszerű DTO (Data Transfer Object) osztályokat. Ne passzolj végig komplex, adatbázis-specifikus objektumokat.
4. A függőségeket „injektáld”: Ahogy a példában is láttad, a Repository-t a Service, a Service-t pedig a Controller kapja paraméterként (constructor injection). Ez nem csak tesztelhetővé teszi a kódot, hanem sokkal áttekinthetőbbé is, hogy ki mit használ.

Összegzés

A réteges architektúra nem egy cél, hanem egy eszköz a karbantartható, clean kód megírásához. Ne úgy állj hozzá, hogy „mostantól minden 5 rétegű lesz”, hanem úgy, hogy „ezt a funkciót hol a leglogikusabb kezelni?”. Kezdd három alappal (Adat, Üzleti, Prezentáció), és csak akkor bonts ki újabbakat (pl. külön Validator réteg), ha érezni, hogy a kód egy része túlzsúfolttá válik.

A kulcs a gyakorlatban: ma írd meg a lehető legegyszerűbb, de már rétegezett módon. Holnap, amikor egy új funkciót kell hozzáadni, pontosan tudni fogod, hogy a UserService-t kell bővíteni, és nem kell keresgélned egy 1000 soros utils.php fájlban. Így marad időd a valódi problémák megoldására, és nem a káoszban való túlélésre.

További jó kódolást!

A weboldalon megjelenő szöveges és vizuális tartalmak előállításához mesterséges intelligenciát (AI) használunk.