Az Angular és a React.js az egyik legismertebb és legnépszerűbb front-end fejlesztési keretrendszerek, melyek különböző megközelítéseket alkalmaznak az alkalmazások kezelésére és karbantartására. Mindkét technológia különböző előnyökkel rendelkezik, de más és más megoldásokat kínál a bonyolult alkalmazások hatékony kezelésére. Az NgRx és a React.js ezen belül fontos szerepet játszanak a komplex állapotkezelésben és az aszinkron adatkezelésben, és mindkettő megoldást kínál az alkalmazás tervezésének és karbantartásának egyszerűsítésére.

Az NgRx az Angular keretrendszerhez készült, és egy rendkívül fejlett eszközkészletet kínál a bonyolult állapotkezeléshez. Ez egy absztrakciós réteget ad a már így is összetett eszközkészlethez, mint például az RxJS. Ha alkalmazásunk három vagy több bemeneti adatfolyammal dolgozik, akkor az NgRx hatékonyan alkalmazható, mivel az események kezelésének költségei megnövekedhetnek, és érdemes új kódolási paradigmát bevezetni. Azonban a legtöbb alkalmazás csupán két bemeneti adatfolyammal dolgozik: REST API-kkal és felhasználói inputtal. Ezért az NgRx használata nem minden esetben szükséges, különösen ha nem olyan komplex alkalmazásról van szó.

NgRx hasznos lehet offline-first Progressive Web Apps (PWA) esetén, ahol bonyolult állapotokat kell tárolni és helyreállítani. Emellett bizonyos niche vállalati alkalmazások esetén is előnyös lehet, ahol speciális igények merülnek fel. Az NgRx használatának alapja az aszinkron adatfolyamok kezelése, és az architektúra alapvetően az akciók, hatások és szelektorok köré épül. Az akciók diszpécselésével és végrehajtásával lehet adatokat tárolni, vagy szerverekkel interakcióba lépni. Az ilyen típusú eszközkészletek leginkább a nagyobb, összetettebb alkalmazások számára előnyösek, ahol a kód kezelhetősége és tesztelhetősége fontos szempont.

A React.js architektúrája ezzel szemben a Flux mintát követi, és bár az alapvető megközelítés az öröklődési fa kezelésére épít, később bevezetésre került a react-redux könyvtár, amely lehetővé teszi, hogy az egyes komponensek közvetlenül az adattárolóból olvassanak és írjanak anélkül, hogy végig kellene járniuk a komponensfa struktúráját. A React tehát más irányba halad, mint az Angular, mivel a központi állapotkezelést egy globális tároló (store) biztosítja, amit bárhol elérhetünk, így elkerülve a komponensek közötti kommunikáció bonyolult struktúráját.

A React alkalmazás architektúrája szintén a komponensek és konténerek hierarchiájára épül, amelyek szigorú fa-struktúrában szerveződnek. Az első verziókban a komponensek közötti adatkommunikáció bonyolult volt, mivel minden adatot kézzel kellett átadni a komponenseken keresztül, azonban a react-redux bevezetésével ez a probléma jelentősen egyszerűsödött. Az architektúra egyszerűsége és tisztasága lehetőséget ad arra, hogy az alkalmazás hatékonyan és könnyen bővíthető legyen, különösen, ha jól van kialakítva a komponensalapú állapotkezelés.

Míg az Angular frissítései és új funkciói rendszeresen érkeznek, és a közösség folyamatosan dolgozik a jobb teljesítmény és a felhasználói élmény javításán, addig a React közössége egy egyszerűbb, de nem kevésbé hatékony megközelítést követ, ami gyakran könnyebben alkalmazható kisebb és közepes méretű projektekben. Az Angular esetében a folyamatos frissítések és az új funkciók (például az Angular Signals) ugyanakkor jelentős hatással vannak az alkalmazások működésére és a fejlesztők munkájára.

A jövőbeli Angular fejlesztések, mint például az Angular Signals, amelyek a natív JavaScript-tel működnek, segítenek a jobb reaktivitásban és memória kezeléseben. Az Angular folyamatosan törekszik arra, hogy csökkentse a renderelési időket, és javítja az alkalmazások válaszidejét, például a Server-Side Rendering (SSR) segítségével. Ez lehetővé teszi, hogy az alkalmazás gyorsabban töltődjön be, miközben csökkenti a kliensoldali erőforrások terhelését.

Az Angular és React között tehát az alapvető különbség a megközelítésben és az alkalmazott mintákban rejlik. Míg az Angular komplex eszközkészletet és szigorúbb architektúrát kínál a nagyobb alkalmazások számára, addig a React inkább a könnyebb, egyszerűbb megoldásokat helyezi előtérbe, amelyeket gyorsan implementálhatunk. Az NgRx és a React-redux könyvtárak pedig mindkét környezetben lehetővé teszik a központi állapotkezelést, amely kulcsfontosságú lehet a skálázható és fenntartható alkalmazások fejlesztésében.

A fejlesztők számára fontos, hogy a megfelelő eszközt válasszák ki az alkalmazás igényei alapján. Az alkalmazás állapotának kezelése, a megfelelő reakciókészség és a teljesítmény optimalizálása mind olyan tényezők, amelyek alapvetően befolyásolják az alkalmazás sikerességét és fenntarthatóságát. A technológiai választásnak figyelembe kell vennie az alkalmazás méretét, a csapat tapasztalatát, valamint a hosszú távú fenntartás szempontjait.

Miért fontos verzionálni az API-kat és hogyan implementáljuk azokat?

A webalkalmazások fejlesztése során gyakran találkozunk a REST és a GraphQL API-k kérdésével. Az API-k hatékonyan kapcsolják össze a frontend és backend rendszereket, és biztosítják, hogy az adatforgalom gördülékenyen történjen. Az API-kat nem csupán egyszerű adatkezelési eszközként kell tekinteni, hanem egy olyan struktúraként, amely lehetővé teszi a verziók kezelését, a kompatibilitás fenntartását és a jövőbeli fejlesztéseket.

Az Express alkalmazás konfigurálása rendkívül egyszerű, mivel a use() metódussal gyorsan hozzáadhatunk middleware-eket, mint például a cors, express.json(), logger, és compression. A következő példában láthatjuk, hogyan építhetjük fel az alapvető szerver konfigurációt:

typescript
import api from './api'
const app = express() app.use(cors()) app.use(express.json()) app.use(express.urlencoded({ extended: true })) app.use(logger('dev')) app.use(compression()) app.use('/', express.static(path.join(__dirname, '../public'), { redirect: false })) app.use(api) export default app

Ebben az esetben a middleware-eket egyszerűen alkalmazzuk, és konfiguráljuk azokat, amelyeket az alkalmazásunk működéséhez szükségesnek tartunk. Itt szolgáltatjuk a statikus fájlokat a / útvonalon, és definiáljuk az API útvonalakat is, például az api.ts fájlban.

A REST API verzionálása szintén alapvető fontosságú. Ahogy egy API nyilvános lesz, gyakran nem lehetséges, hogy egyszerűen eltávolítsuk a régebbi verziókat, mert a meglévő kliensek megsérülhetnek. A legjobb gyakorlat tehát az, hogy verzionáljuk minden API-t. Ha elérkezik az idő, amikor új funkciókat kell bevezetni, akkor a régi verziók megtartása mellett az új verziót (pl. v2) is elérhetővé tehetjük.

A verziózás segíti abban, hogy folyamatosan fejlődhessünk, miközben biztosítjuk, hogy a már működő alkalmazások számára is biztosítva legyen a stabil működés. A verziók kezelése lehetővé teszi, hogy a fejlesztés dinamikusan haladjon előre, miközben a régi API-kat továbbra is használhatják a felhasználók. Ez különösen fontos, mivel nem mindig van lehetőség minden változtatást azonnal bevezetni a meglévő API-kba.

A következő példa a verzionált útvonalak kezelésére vonatkozik:

typescript
import { Router } from 'express' import api_v1 from './v1' import api_v2 from './v2' const api = Router() api.use('/v1', api_v1) api.use('/v2', api_v2) export default api

Ebben az esetben a v1 és v2 verziók külön útvonalakat képviselnek, amelyeken keresztül a megfelelő verziókat érhetjük el.

A verziók és útvonalak kezelésének másik fontos aspektusa, hogy a router.use() metódust alkalmazva egyszerűen konfigurálhatjuk a különböző verziókat és azok alútvonalait. Például a v2 verzióban a következő módon érhetjük el a users végpontot:

typescript
import { Router } from 'express' import userRouter from './routes/userRouter' const router = Router() router.use('/users?', userRouter) export default router

A kérdőjel (?) használata lehetővé teszi, hogy mind a /user, mind a /users végpontokat kezeljük, ezzel elkerülve a hibákat és biztosítva, hogy a fejlesztők rugalmasan választhassák ki a megfelelő szótövet az API-k végpontjaiban.

Ezután a userRouter konfigurációjában megvalósíthatjuk az alapvető CRUD műveleteket, mint a GET, POST, PUT és DELETE:

typescript
const router = Router()
router.get('/', async (req: Request, res: Response) => {}) router.post('/', async (req: Request, res: Response) => {}) router.get('/:userId', async (req: Request, res: Response) => {}) router.put('/:userId', async (req: Request, res: Response) => {}) export default router

A route paraméterek kezeléséhez a req.params.userId segítségével hozzáférhetünk az egyes végpontokhoz rendelt paraméterekhez. Mivel a példában minden útvonal aszinkron, minden egyes művelet előtt várakozunk az adatbázis-hívások befejeződésére.

A GraphQL implementációja más jellegű kihívásokat jelenthet, mivel a GraphQL kérés feldolgozása során az egyes mezőkhez tartozó válaszokat különböző resolverek kezelhetik. Az alábbi kódrészlet mutatja, hogyan néz ki egy GraphQL resolver:

typescript
export const resolvers = { Query: { me: () => ..., user: () => ..., users: () => ..., }, Mutation: { login: () => ..., createUser: () => ..., updateUser: () => ..., }, }

A Query és Mutation típusokhoz kapcsolódó resolverek a különböző adatokat kérhetik le vagy módosíthatják azokat. Mivel minden egyes mezőhöz külön resolver tartozik, a rendszer rendkívül rugalmas és lehetővé teszi a bonyolult adatlekérdezések hatékony kezelését.

A nem-skalár típusok, mint például tömbök és enumok esetében szükség van egy átalakítási mechanizmusra, hogy a GraphQL megfelelően feldolgozhassa a válaszokat. Ehhez külön-külön kell megadnunk a szükséges transzformáló logikát.

Az alkalmazások szerveroldali logikáját nem érdemes közvetlenül az API rétegbe helyezni, mivel az API réteg inkább az adatok átalakítására és az üzleti logika rétegének hívására szolgál. Az üzleti logika implementálásához használhatunk egyszerű Node.js és TypeScript megoldásokat, például a userService.ts fájlban, ahol a createNewUser funkciót implementálhatjuk:

typescript
import { IUser, User } from '../models/user'
export async function createNewUser(userData: IUser): Promise<User> { // felhasználó létrehozása }

Ez a funkció az IUser típusú adatot várja és visszaadja a User típusú objektumot. Az ilyen típusú üzleti logikát érdemes külön szolgáltatásként kezelni, hogy az API réteg tiszta maradjon és az üzleti logika a lehető legjobb módon legyen elválasztva a többi kódrétegtől.