Sužinokite, kaip šešiakampė architektūra, dar vadinama prievadų ir adapterių architektūra, gali pagerinti jūsų programų palaikomumą, testuojamumą ir lankstumą. Šis vadovas pateikia praktinių pavyzdžių ir naudingų įžvalgų programuotojams visame pasaulyje.
Šešiakampė architektūra: praktinis prievadų ir adapterių vadovas
Nuolat besikeičiančioje programinės įrangos kūrimo srityje, tvirtų, palaikomų ir testuojamų programų kūrimas yra svarbiausias dalykas. Šešiakampė architektūra, taip pat žinoma kaip prievadų ir adapterių architektūra, yra architektūrinis modelis, kuris sprendžia šias problemas atsiejant pagrindinę programos verslo logiką nuo jos išorinių priklausomybių. Šiuo vadovu siekiama suteikti išsamų supratimą apie šešiakampę architektūrą, jos privalumus ir praktines įgyvendinimo strategijas programuotojams visame pasaulyje.
Kas yra šešiakampė architektūra?
Šešiakampė architektūra, kurią sukūrė Alistair Cockburn, remiasi idėja izoliuoti programos branduolio verslo logiką nuo jos išorinio pasaulio. Ši izoliacija pasiekiama naudojant prievadus ir adapterius.
- Branduolys (programa): Atstovauja jūsų programos šerdį, kurioje yra verslo logika ir domenų modeliai. Ji turėtų būti nepriklausoma nuo jokios konkrečios technologijos ar karkaso.
- Prievadai: Apibrėžia sąsajas, kurias programos branduolys naudoja sąveikai su išoriniu pasauliu. Tai yra abstraktūs apibrėžimai, kaip programa sąveikauja su išorinėmis sistemomis, tokiomis kaip duomenų bazės, vartotojo sąsajos ar pranešimų eilės. Prievadai gali būti dviejų tipų:
- Inicijuojantys (pirminiai) prievadai: Apibrėžia sąsajas, per kurias išoriniai veikėjai (pvz., vartotojai, kitos programos) gali inicijuoti veiksmus programos branduolyje.
- Valdomi (antriniai) prievadai: Apibrėžia sąsajas, kurias programos branduolys naudoja sąveikai su išorinėmis sistemomis (pvz., duomenų bazėmis, pranešimų eilėmis).
- Adapteriai: Įgyvendina sąsajas, apibrėžtas prievadų. Jie veikia kaip vertėjai tarp programos branduolio ir išorinių sistemų. Yra du adapterių tipai:
- Inicijuojantys (pirminiai) adapteriai: Įgyvendina inicijuojančius prievadus, versdami išorines užklausas į komandas ar užklausas, kurias gali suprasti programos branduolys. Pavyzdžiai: vartotojo sąsajos komponentai (pvz., žiniatinklio valdikliai), komandinės eilutės sąsajos arba pranešimų eilių klausytojai.
- Valdomi (antriniai) adapteriai: Įgyvendina valdomus prievadus, versdami programos branduolio užklausas į konkrečias sąveikas su išorinėmis sistemomis. Pavyzdžiai: duomenų bazės prieigos objektai, pranešimų eilių gamintojai arba API klientai.
Įsivaizduokite tai taip: programos branduolys yra centre, apsuptas šešiakampio apvalkalo. Prievadai yra įėjimo ir išėjimo taškai šiame apvalkale, o adapteriai jungiasi prie šių prievadų, sujungdami branduolį su išoriniu pasauliu.
Pagrindiniai šešiakampės architektūros principai
Šešiakampės architektūros efektyvumą lemia keli pagrindiniai principai:
- Priklausomybių inversija: Programos branduolys priklauso nuo abstrakcijų (prievadų), o ne nuo konkrečių įgyvendinimų (adapterių). Tai yra pagrindinis SOLID projektavimo principas.
- Aiškios sąsajos: Prievadai aiškiai apibrėžia ribas tarp branduolio ir išorinio pasaulio, skatindami sutartimi pagrįstą požiūrį į integraciją.
- Testuojamumas: Atsiejus branduolį nuo išorinių priklausomybių, tampa lengviau testuoti verslo logiką izoliuotai, naudojant imitacinius prievadų įgyvendinimus.
- Lankstumas: Adapterius galima keisti nepaveikiant programos branduolio, kas leidžia lengvai prisitaikyti prie besikeičiančių technologijų ar reikalavimų. Įsivaizduokite, kad reikia pereiti nuo MySQL prie PostgreSQL; reikia pakeisti tik duomenų bazės adapterį.
Šešiakampės architektūros naudojimo privalumai
Šešiakampės architektūros pritaikymas suteikia daug privalumų:
- Pagerintas testuojamumas: Atsakomybių atskyrimas žymiai palengvina vienetinių testų rašymą pagrindinei verslo logikai. Imituojant prievadus, galite izoliuoti branduolį ir jį kruopščiai išbandyti, nepriklausomai nuo išorinių sistemų. Pavyzdžiui, mokėjimų apdorojimo modulį galima išbandyti imituojant mokėjimo šliuzo prievadą, simuliuojant sėkmingas ir nesėkmingas transakcijas, faktiškai neprisijungiant prie tikrojo šliuzo.
- Padidintas palaikomumas: Išorinių sistemų ar technologijų pakeitimai turi minimalų poveikį programos branduoliui. Adapteriai veikia kaip izoliaciniai sluoksniai, saugantys branduolį nuo išorinio nepastovumo. Apsvarstykite scenarijų, kai trečiosios šalies API, naudojama SMS pranešimams siųsti, pakeičia savo formatą ar autentifikavimo metodą. Reikia atnaujinti tik SMS adapterį, paliekant programos branduolį nepaliestą.
- Padidintas lankstumas: Adapterius galima lengvai keisti, leidžiant prisitaikyti prie naujų technologijų ar reikalavimų be didelių pakeitimų. Tai palengvina eksperimentavimą ir inovacijas. Įmonė gali nuspręsti perkelti savo duomenų saugyklą iš tradicinės reliacinės duomenų bazės į NoSQL duomenų bazę. Su šešiakampe architektūra reikia pakeisti tik duomenų bazės adapterį, sumažinant trikdžius pagrindinei programai.
- Sumažintas susiejimas: Programos branduolys yra atsietas nuo išorinių priklausomybių, kas veda prie modularesnio ir vientisesnio dizaino. Dėl to kodo bazę lengviau suprasti, keisti ir plėsti.
- Nepriklausomas kūrimas: Skirtingos komandos gali dirbti su programos branduoliu ir adapteriais nepriklausomai, skatinant lygiagretų kūrimą ir greitesnį pateikimą į rinką. Pavyzdžiui, viena komanda galėtų sutelkti dėmesį į pagrindinės užsakymų apdorojimo logikos kūrimą, o kita komanda kurtų vartotojo sąsają ir duomenų bazės adapterius.
Šešiakampės architektūros įgyvendinimas: praktinis pavyzdys
Iliustruokime šešiakampės architektūros įgyvendinimą supaprastintu vartotojo registracijos sistemos pavyzdžiu. Aiškumui naudosime hipotetinę programavimo kalbą (panašią į Java ar C#).
1. Apibrėžkite branduolį (programą)
Programos branduolyje yra verslo logika, skirta naujo vartotojo registracijai.
// Core/UserService.java (arba 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) {
// Patikrinti vartotojo įvestį
ValidationResult validationResult = userValidator.validate(username, password, email);
if (!validationResult.isValid()) {
return Result.failure(validationResult.getErrorMessage());
}
// Patikrinti, ar vartotojas jau egzistuoja
if (userRepository.findByUsername(username).isPresent()) {
return Result.failure("Vartotojo vardas jau egzistuoja");
}
// Maišyti slaptažodį
String hashedPassword = passwordHasher.hash(password);
// Sukurti naują vartotoją
User user = new User(username, hashedPassword, email);
// Išsaugoti vartotoją saugykloje
userRepository.save(user);
return Result.success(user);
}
}
2. Apibrėžkite prievadus
Apibrėžiame prievadus, kuriuos programos branduolys naudoja sąveikai su išoriniu pasauliu.
// Ports/UserRepository.java (arba UserRepository.cs)
public interface UserRepository {
Optional<User> findByUsername(String username);
void save(User user);
}
// Ports/PasswordHasher.java (arba PasswordHasher.cs)
public interface PasswordHasher {
String hash(String password);
}
//Ports/UserValidator.java (arba UserValidator.cs)
public interface UserValidator{
ValidationResult validate(String username, String password, String email);
}
//Ports/ValidationResult.java (arba ValidationResult.cs)
public interface ValidationResult{
boolean isValid();
String getErrorMessage();
}
3. Apibrėžkite adapterius
Įgyvendiname adapterius, kurie sujungia programos branduolį su konkrečiomis technologijomis.
// Adapters/DatabaseUserRepository.java (arba 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) {
// Įgyvendinimas naudojant JDBC, JPA ar kitą duomenų bazės prieigos technologiją
// ...
return Optional.empty(); // Vietos rezervavimo ženklas
}
@Override
public void save(User user) {
// Įgyvendinimas naudojant JDBC, JPA ar kitą duomenų bazės prieigos technologiją
// ...
}
}
// Adapters/BCryptPasswordHasher.java (arba BCryptPasswordHasher.cs)
public class BCryptPasswordHasher implements PasswordHasher {
@Override
public String hash(String password) {
// Įgyvendinimas naudojant BCrypt biblioteką
// ...
return "hashedPassword"; //Vietos rezervavimo ženklas
}
}
//Adapters/SimpleUserValidator.java (arba SimpleUserValidator.cs)
public class SimpleUserValidator implements UserValidator {
@Override
public ValidationResult validate(String username, String password, String email){
//Paprasta patvirtinimo logika
if (username == null || username.isEmpty()) {
return new SimpleValidationResult(false, "Vartotojo vardas negali būti tuščias");
}
if (password == null || password.length() < 8) {
return new SimpleValidationResult(false, "Slaptažodis turi būti bent 8 simbolių ilgio");
}
if (email == null || !email.contains("@")) {
return new SimpleValidationResult(false, "Neteisingas el. pašto formatas");
}
return new SimpleValidationResult(true, null);
}
}
//Adapters/SimpleValidationResult.java (arba 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;
}
}
//Adapters/WebUserController.java (arba WebUserController.cs)
//Inicijuojantis adapteris - apdoroja užklausas iš žiniatinklio
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 sėkminga!";
} else {
return "Registracija nepavyko: " + result.getFailure();
}
}
}
4. Kompozicija
Visko sujungimas. Atkreipkite dėmesį, kad ši kompozicija (priklausomybių įpurškimas) paprastai vyksta programos įėjimo taške arba priklausomybių įpurškimo konteineryje.
//Pagrindinė klasė arba priklausomybių įpurškimo konfigūracija
public class Main {
public static void main(String[] args) {
// Sukurti adapterių egzempliorius
DatabaseConnection databaseConnection = new DatabaseConnection("jdbc:mydb://localhost:5432/users", "user", "password");
DatabaseUserRepository userRepository = new DatabaseUserRepository(databaseConnection);
BCryptPasswordHasher passwordHasher = new BCryptPasswordHasher();
SimpleUserValidator userValidator = new SimpleUserValidator();
// Sukurti programos branduolio egzempliorių, įpurškiant adapterius
UserService userService = new UserService(userRepository, passwordHasher, userValidator);
//Sukurti inicijuojantį adapterį ir prijungti jį prie paslaugos
WebUserController userController = new WebUserController(userService);
//Dabar galite tvarkyti vartotojo registracijos užklausas per userController
String result = userController.registerUser("john.doe", "P@sswOrd123", "john.doe@example.com");
System.out.println(result);
}
}
//DatabaseConnection yra paprasta klasė tik demonstraciniais tikslais
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;
}
// ... metodai prisijungti prie duomenų bazės (neįgyvendinta dėl trumpumo)
}
//Result klasė (panaši į Either funkcinėje programavime)
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("Rezultatas yra nesėkmė");
}
return success;
}
public E getFailure() {
if (isSuccess) {
throw new IllegalStateException("Rezultatas yra sėkmė");
}
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;
}
// getter'iai ir setter'iai (praleista dėl trumpumo)
}
Paaiškinimas:
UserService
atstovauja pagrindinę verslo logiką. Ji priklauso nuoUserRepository
,PasswordHasher
irUserValidator
sąsajų (prievadų).DatabaseUserRepository
,BCryptPasswordHasher
irSimpleUserValidator
yra adapteriai, kurie įgyvendina atitinkamus prievadus naudojant konkrečias technologijas (duomenų bazę, BCrypt ir pagrindinę patvirtinimo logiką).WebUserController
yra inicijuojantis adapteris, kuris apdoroja žiniatinklio užklausas ir sąveikauja suUserService
.- Pagrindinis metodas sukomponuoja programą, sukuriant adapterių egzempliorius ir įpurškiant juos į programos branduolį.
Pažangesni aspektai ir gerosios praktikos
Nors pagrindiniai šešiakampės architektūros principai yra paprasti, yra keletas pažangesnių aspektų, kuriuos reikia turėti omenyje:
- Tinkamo prievadų detalumo pasirinkimas: Nustatyti tinkamą abstrakcijos lygį prievadams yra labai svarbu. Per smulkūs prievadai gali sukelti nereikalingą sudėtingumą, o per stambūs prievadai gali apriboti lankstumą. Apibrėždami savo prievadus, apsvarstykite kompromisus tarp paprastumo ir pritaikomumo.
- Transakcijų valdymas: Kai dirbama su keliomis išorinėmis sistemomis, užtikrinti transakcijų nuoseklumą gali būti sudėtinga. Apsvarstykite galimybę naudoti paskirstytų transakcijų valdymo metodus arba įgyvendinti kompensuojančias transakcijas, kad išlaikytumėte duomenų vientisumą. Pavyzdžiui, jei registruojant vartotoją reikia sukurti paskyrą atskiroje atsiskaitymo sistemoje, turite užtikrinti, kad abi operacijos pavyktų arba nepavyktų kartu.
- Klaidų tvarkymas: Įgyvendinkite tvirtus klaidų tvarkymo mechanizmus, kad grakščiai tvarkytumėte gedimus išorinėse sistemose. Naudokite grandinės pertraukiklius arba pakartojimo mechanizmus, kad išvengtumėte kaskadinių gedimų. Kai adapteris negali prisijungti prie duomenų bazės, programa turėtų grakščiai apdoroti klaidą ir galbūt bandyti prisijungti iš naujo arba pateikti vartotojui informatyvų klaidos pranešimą.
- Testavimo strategijos: Naudokite vienetinių testų, integracinių testų ir „end-to-end“ testų derinį, kad užtikrintumėte savo programos kokybę. Vienetiniai testai turėtų sutelkti dėmesį į pagrindinę verslo logiką, o integraciniai testai turėtų patikrinti sąveiką tarp branduolio ir adapterių.
- Priklausomybių įpurškimo karkasai: Naudokite priklausomybių įpurškimo karkasus (pvz., Spring, Guice), kad valdytumėte priklausomybes tarp komponentų ir supaprastintumėte programos komponavimą. Šie karkasai automatizuoja priklausomybių kūrimo ir įpurškimo procesą, sumažindami standartinio kodo kiekį ir pagerindami palaikomumą.
- CQRS (Komandų ir užklausų atsakomybės atskyrimas): Šešiakampė architektūra gerai dera su CQRS, kai atskiriate savo programos skaitymo ir rašymo modelius. Tai gali dar labiau pagerinti našumą ir mastelį, ypač sudėtingose sistemose.
Realaus pasaulio šešiakampės architektūros naudojimo pavyzdžiai
Daugelis sėkmingų įmonių ir projektų pritaikė šešiakampę architektūrą kurdami tvirtas ir palaikomas sistemas:
- Elektroninės prekybos platformos: Elektroninės prekybos platformos dažnai naudoja šešiakampę architektūrą, kad atsietų pagrindinę užsakymų apdorojimo logiką nuo įvairių išorinių sistemų, tokių kaip mokėjimo šliuzai, siuntimo teikėjai ir atsargų valdymo sistemos. Tai leidžia joms lengvai integruoti naujus mokėjimo metodus ar siuntimo parinktis, netrikdant pagrindinės funkcijos.
- Finansinės programos: Finansinės programos, tokios kaip bankininkystės sistemos ir prekybos platformos, gauna naudos iš šešiakampės architektūros siūlomo testuojamumo ir palaikomumo. Pagrindinę finansinę logiką galima kruopščiai išbandyti izoliuotai, o adapterius galima naudoti prisijungimui prie įvairių išorinių paslaugų, tokių kaip rinkos duomenų teikėjai ir kliringo namai.
- Mikropaslaugų architektūros: Šešiakampė architektūra natūraliai tinka mikropaslaugų architektūroms, kur kiekviena mikropaslauga atstovauja apibrėžtą kontekstą su savo pagrindine verslo logika ir išorinėmis priklausomybėmis. Prievadai ir adapteriai suteikia aiškią sutartį komunikacijai tarp mikropaslaugų, skatinant silpną susiejimą ir nepriklausomą diegimą.
- Senų sistemų modernizavimas: Šešiakampė architektūra gali būti naudojama laipsniškai modernizuoti senas sistemas, apgaubiant esamą kodą adapteriais ir įvedant naują branduolio logiką už prievadų. Tai leidžia laipsniškai keisti senos sistemos dalis, neperrašant visos programos.
Iššūkiai ir kompromisai
Nors šešiakampė architektūra siūlo didelių privalumų, svarbu pripažinti susijusius iššūkius ir kompromisus:
- Padidėjęs sudėtingumas: Šešiakampės architektūros įgyvendinimas gali įvesti papildomų abstrakcijos sluoksnių, kurie gali padidinti pradinį kodo bazės sudėtingumą.
- Mokymosi kreivė: Programuotojams gali prireikti laiko suprasti prievadų ir adapterių sąvokas ir kaip jas efektyviai taikyti.
- Perteklinio projektavimo potencialas: Svarbu vengti perteklinio projektavimo, kuriant nereikalingus prievadus ir adapterius. Pradėkite nuo paprasto dizaino ir palaipsniui didinkite sudėtingumą pagal poreikį.
- Našumo aspektai: Papildomi abstrakcijos sluoksniai gali potencialiai sukelti tam tikrą našumo praradimą, nors daugumoje programų tai paprastai yra nereikšminga.
Labai svarbu atidžiai įvertinti šešiakampės architektūros privalumus ir iššūkius atsižvelgiant į jūsų konkretaus projekto reikalavimus ir komandos galimybes. Tai nėra sidabrinė kulka, ir tai gali būti ne geriausias pasirinkimas kiekvienam projektui.
Išvada
Šešiakampė architektūra, pabrėžianti prievadus ir adapterius, suteikia galingą požiūrį į palaikomų, testuojamų ir lanksčių programų kūrimą. Atsiejant pagrindinę verslo logiką nuo išorinių priklausomybių, ji leidžia lengvai prisitaikyti prie besikeičiančių technologijų ir reikalavimų. Nors yra iššūkių ir kompromisų, kuriuos reikia apsvarstyti, šešiakampės architektūros privalumai dažnai nusveria išlaidas, ypač sudėtingoms ir ilgaamžėms programoms. Priimdami priklausomybių inversijos ir aiškių sąsajų principus, galite sukurti sistemas, kurios yra atsparesnės, lengviau suprantamos ir geriau pasirengusios atitikti šiuolaikinės programinės įrangos srities reikalavimus.
Šis vadovas pateikė išsamią šešiakampės architektūros apžvalgą, nuo jos pagrindinių principų iki praktinių įgyvendinimo strategijų. Mes skatiname jus toliau tyrinėti šias sąvokas ir eksperimentuoti taikant jas savo projektuose. Investicija į šešiakampės architektūros mokymąsi ir pritaikymą neabejotinai atsipirks ilgalaikėje perspektyvoje, lemdama aukštesnės kokybės programinę įrangą ir labiau patenkintas kūrėjų komandas.
Galiausiai, tinkamos architektūros pasirinkimas priklauso nuo konkrečių jūsų projekto poreikių. Priimdami sprendimą, atsižvelkite į sudėtingumo, ilgaamžiškumo ir palaikomumo reikalavimus. Šešiakampė architektūra suteikia tvirtą pagrindą kurti tvirtas ir pritaikomas programas, tačiau tai tik vienas įrankis programinės įrangos architekto įrankių dėžėje.