Hrvatski

Saznajte kako heksagonalna arhitektura, poznata i kao portovi i adapteri, može poboljšati održivost, testabilnost i fleksibilnost vaših aplikacija. Ovaj vodič pruža praktične primjere i korisne savjete za programere širom svijeta.

Heksagonalna arhitektura: Praktični vodič za portove i adaptere

U svijetu razvoja softvera koji se neprestano mijenja, izrada robusnih aplikacija koje se mogu održavati i testirati od presudne je važnosti. Heksagonalna arhitektura, poznata i kao portovi i adapteri, arhitektonski je obrazac koji rješava te probleme odvajanjem temeljne poslovne logike aplikacije od njezinih vanjskih ovisnosti. Cilj ovog vodiča je pružiti sveobuhvatno razumijevanje heksagonalne arhitekture, njezinih prednosti i praktičnih strategija implementacije za programere na globalnoj razini.

Što je heksagonalna arhitektura?

Heksagonalnu arhitekturu, koju je osmislio Alistair Cockburn, temelji se na ideji izoliranja temeljne poslovne logike aplikacije od vanjskog svijeta. Ta se izolacija postiže korištenjem portova i adaptera.

Zamislite to ovako: temeljna aplikacija nalazi se u središtu, okružena heksagonalnom ljuskom. Portovi su ulazne i izlazne točke na toj ljusci, a adapteri se priključuju na te portove, povezujući jezgru s vanjskim svijetom.

Ključna načela heksagonalne arhitekture

Nekoliko ključnih načela podupire učinkovitost heksagonalne arhitekture:

Prednosti korištenja heksagonalne arhitekture

Usvajanje heksagonalne arhitekture nudi brojne prednosti:

Implementacija heksagonalne arhitekture: Praktični primjer

Ilustrirajmo implementaciju heksagonalne arhitekture pojednostavljenim primjerom sustava za registraciju korisnika. Koristit ćemo hipotetski programski jezik (sličan Javi ili C#) radi jasnoće.

1. Definiranje jezgre (aplikacije)

Jezgra aplikacije sadrži poslovnu logiku za registraciju novog korisnika.


// Jezgra/UserService.java (ili UserService.cs)
public class UserService {
    private final UserRepository userRepository;
    private final PasswordHasher passwordHasher;
    private final UserValidator userValidator;

    public UserService(UserRepository userRepository, PasswordHasher passwordHasher, UserValidator userValidator) {
        this.userRepository = userRepository;
        this.passwordHasher = passwordHasher;
        this.userValidator = userValidator;
    }

    public Result<User, String> registerUser(String username, String password, String email) {
        // Validacija korisničkog unosa
        ValidationResult validationResult = userValidator.validate(username, password, email);
        if (!validationResult.isValid()) {
            return Result.failure(validationResult.getErrorMessage());
        }

        // Provjera postoji li korisnik već
        if (userRepository.findByUsername(username).isPresent()) {
            return Result.failure("Korisničko ime već postoji");
        }

        // Heširanje lozinke
        String hashedPassword = passwordHasher.hash(password);

        // Stvaranje novog korisnika
        User user = new User(username, hashedPassword, email);

        // Spremanje korisnika u repozitorij
        userRepository.save(user);

        return Result.success(user);
    }
}

2. Definiranje portova

Definiramo portove koje temeljna aplikacija koristi za interakciju s vanjskim svijetom.


// Portovi/UserRepository.java (ili UserRepository.cs)
public interface UserRepository {
    Optional<User> findByUsername(String username);
    void save(User user);
}

// Portovi/PasswordHasher.java (ili PasswordHasher.cs)
public interface PasswordHasher {
    String hash(String password);
}

//Portovi/UserValidator.java (ili UserValidator.cs)
public interface UserValidator{
  ValidationResult validate(String username, String password, String email);
}

//Portovi/ValidationResult.java (ili ValidationResult.cs)
public interface ValidationResult{
  boolean isValid();
  String getErrorMessage();
}

3. Definiranje adaptera

Implementiramo adaptere koji povezuju temeljnu aplikaciju s određenim tehnologijama.


// Adapteri/DatabaseUserRepository.java (ili DatabaseUserRepository.cs)
public class DatabaseUserRepository implements UserRepository {
    private final DatabaseConnection databaseConnection;

    public DatabaseUserRepository(DatabaseConnection databaseConnection) {
        this.databaseConnection = databaseConnection;
    }

    @Override
    public Optional<User> findByUsername(String username) {
        // Implementacija koristeći JDBC, JPA ili drugu tehnologiju za pristup bazi podataka
        // ...
        return Optional.empty(); // Privremeno rješenje
    }

    @Override
    public void save(User user) {
        // Implementacija koristeći JDBC, JPA ili drugu tehnologiju za pristup bazi podataka
        // ...
    }
}

// Adapteri/BCryptPasswordHasher.java (ili BCryptPasswordHasher.cs)
public class BCryptPasswordHasher implements PasswordHasher {
    @Override
    public String hash(String password) {
        // Implementacija koristeći BCrypt biblioteku
        // ...
        return "hashedPassword"; //Privremeno rješenje
    }
}

//Adapteri/SimpleUserValidator.java (ili SimpleUserValidator.cs)
public class SimpleUserValidator implements UserValidator {
  @Override
  public ValidationResult validate(String username, String password, String email){
    // Jednostavna logika validacije
     if (username == null || username.isEmpty()) {
            return new SimpleValidationResult(false, "Korisničko ime ne smije biti prazno");
        }
        if (password == null || password.length() < 8) {
            return new SimpleValidationResult(false, "Lozinka mora imati najmanje 8 znakova");
        }
        if (email == null || !email.contains("@")) {
            return new SimpleValidationResult(false, "Nevažeći format e-pošte");
        }

        return new SimpleValidationResult(true, null);
  }
}

//Adapteri/SimpleValidationResult.java (ili SimpleValidationResult.cs)
public class SimpleValidationResult implements ValidationResult {
  private final boolean valid;
  private final String errorMessage;

    public SimpleValidationResult(boolean valid, String errorMessage) {
        this.valid = valid;
        this.errorMessage = errorMessage;
    }
  @Override
  public boolean isValid(){
    return valid;
  }

  @Override
  public String getErrorMessage(){
    return errorMessage;
  }
}



//Adapteri/WebUserController.java (ili WebUserController.cs)
// Pokretački adapter - obrađuje zahtjeve s weba
public class WebUserController {
    private final UserService userService;

    public WebUserController(UserService userService) {
        this.userService = userService;
    }

    public String registerUser(String username, String password, String email) {
        Result<User, String> result = userService.registerUser(username, password, email);
        if (result.isSuccess()) {
            return "Registracija uspješna!";
        } else {
            return "Registracija neuspješna: " + result.getFailure();
        }
    }
}


4. Kompozicija

Povezivanje svega zajedno. Imajte na umu da se ova kompozicija (ubrizgavanje ovisnosti) obično događa na ulaznoj točki aplikacije ili unutar spremnika za ubrizgavanje ovisnosti.


// Glavna klasa ili konfiguracija za ubrizgavanje ovisnosti
public class Main {
    public static void main(String[] args) {
        // Stvaranje instanci adaptera
        DatabaseConnection databaseConnection = new DatabaseConnection("jdbc:mydb://localhost:5432/users", "user", "password");
        DatabaseUserRepository userRepository = new DatabaseUserRepository(databaseConnection);
        BCryptPasswordHasher passwordHasher = new BCryptPasswordHasher();
        SimpleUserValidator userValidator = new SimpleUserValidator();

        // Stvaranje instance temeljne aplikacije, ubrizgavanjem adaptera
        UserService userService = new UserService(userRepository, passwordHasher, userValidator);

        // Stvaranje pokretačkog adaptera i povezivanje s uslugom
        WebUserController userController = new WebUserController(userService);

        // Sada možete obrađivati zahtjeve za registraciju korisnika putem userControllera
        String result = userController.registerUser("john.doe", "P@sswOrd123", "john.doe@example.com");
        System.out.println(result);
    }
}



// DatabaseConnection je jednostavna klasa samo u svrhu demonstracije
class DatabaseConnection {
    private String url;
    private String username;
    private String password;

    public DatabaseConnection(String url, String username, String password) {
        this.url = url;
        this.username = username;
        this.password = password;
    }

    // ... metode za povezivanje s bazom podataka (nisu implementirane radi sažetosti)
}

// Klasa Result (slična Either u funkcionalnom programiranju)
class Result<T, E> {
    private final T success;
    private final E failure;
    private final boolean isSuccess;

    private Result(T success, E failure, boolean isSuccess) {
        this.success = success;
        this.failure = failure;
        this.isSuccess = isSuccess;
    }

    public static <T, E> Result<T, E> success(T value) {
        return new Result<>(value, null, true);
    }

    public static <T, E> Result<T, E> failure(E error) {
        return new Result<>(null, error, false);
    }

    public boolean isSuccess() {
        return isSuccess;
    }

    public T getSuccess() {
        if (!isSuccess) {
            throw new IllegalStateException("Rezultat je neuspjeh");
        }
        return success;
    }

    public E getFailure() {
        if (isSuccess) {
            throw new IllegalStateException("Rezultat je uspjeh");
        }
        return failure;
    }
}

class User {
    private String username;
    private String password;
    private String email;

    public User(String username, String password, String email) {
        this.username = username;
        this.password = password;
        this.email = email;
    }

    // getteri i setteri (izostavljeni radi sažetosti)

}

Objašnjenje:

Napredna razmatranja i najbolje prakse

Iako su osnovna načela heksagonalne arhitekture jednostavna, postoje neka napredna razmatranja koja treba imati na umu:

Primjeri heksagonalne arhitekture u stvarnom svijetu

Mnoge uspješne tvrtke i projekti usvojili su heksagonalnu arhitekturu za izgradnju robusnih sustava koji se mogu održavati:

Izazovi i kompromisi

Iako heksagonalna arhitektura nudi značajne prednosti, važno je prepoznati izazove i kompromise koji su uključeni:

Ključno je pažljivo procijeniti prednosti i izazove heksagonalne arhitekture u kontekstu specifičnih zahtjeva vašeg projekta i sposobnosti tima. To nije srebrni metak i možda nije najbolji izbor za svaki projekt.

Zaključak

Heksagonalna arhitektura, s naglaskom na portove i adaptere, pruža snažan pristup izgradnji održivih, testabilnih i fleksibilnih aplikacija. Odvajanjem temeljne poslovne logike od vanjskih ovisnosti, omogućuje vam da se s lakoćom prilagodite promjenjivim tehnologijama i zahtjevima. Iako postoje izazovi i kompromisi koje treba razmotriti, prednosti heksagonalne arhitekture često nadmašuju troškove, posebno za složene i dugovječne aplikacije. Prihvaćanjem načela inverzije ovisnosti i eksplicitnih sučelja, možete stvoriti sustave koji su otporniji, lakši za razumijevanje i bolje opremljeni za ispunjavanje zahtjeva modernog softverskog krajolika.

Ovaj vodič pružio je sveobuhvatan pregled heksagonalne arhitekture, od njezinih temeljnih načela do praktičnih strategija implementacije. Potičemo vas da dalje istražite ove koncepte i eksperimentirate s njihovom primjenom u vlastitim projektima. Ulaganje u učenje i usvajanje heksagonalne arhitekture nedvojbeno će se isplatiti na duge staze, dovodeći do kvalitetnijeg softvera i zadovoljnijih razvojnih timova.

U konačnici, odabir prave arhitekture ovisi o specifičnim potrebama vašeg projekta. Razmotrite složenost, dugovječnost i zahtjeve za održivost prilikom donošenja odluke. Heksagonalna arhitektura pruža čvrst temelj za izgradnju robusnih i prilagodljivih aplikacija, ali je samo jedan alat u kutiji s alatima softverskog arhitekta.