Design Patterns și Arhitectură Type-Safe în TypeScript Enterprise în TypeScript

Ghid complet pentru modele de design arhitectural în TypeScript: Builder Pattern cu Phantom Types, Factory cu Discriminated Unions și Result Pattern pentru gestionarea sigură a erorilor fără throw.

Sintaxă
type Result<T, E> = { ok: true; value: T } | { ok: false; error: E };

Modelele clasice de proiectare software (GoF – Gang of Four) au fost concepute inițial pentru limbaje cu tipare nominală rigidă precum Java sau C++. În TypeScript, combinarea tipării structurale, a uniunilor discriminate și a parametrilor de tip fantomă (Phantom Types) permite implementarea unor versiuni mult mai elegante, mai sigure și complet verificate la compilare ale acestor modele.

1. Builder Pattern cu Phantom Types (Verificare la Compilare)
// Problema clasică cu Builder: Cum garantăm la compilare că utilizatorul a setat
// câmpurile obligatorii înainte de a apela .build()?
// Soluție: Folosim Phantom Types pentru a urmări starea internă!

interface ComandaFinala {
    client: string;
    total: number;
    adresaLivrare: string;
}

// Folosim un generic 'Stare' pentru a reține câmpurile completate:
class ComandaBuilder<Stare extends { client?: string; total?: number; adresaLivrare?: string } = {}> {
    private date: Partial<ComandaFinala> = {};

    setClient(client: string): ComandaBuilder<Stare & { client: string }> {
        this.date.client = client;
        return this as any;
    }

    setTotal(total: number): ComandaBuilder<Stare & { total: number }> {
        this.date.total = total;
        return this as any;
    }

    setAdresa(adresa: string): ComandaBuilder<Stare & { adresaLivrare: string }> {
        this.date.adresaLivrare = adresa;
        return this as any;
    }

    // Metoda .build() este activată DOAR dacă toate cele 3 chei obligatorii sunt prezente în 'Stare':
    build(this: ComandaBuilder<{ client: string; total: number; adresaLivrare: string }>): ComandaFinala {
        return this.date as ComandaFinala;
    }
}

// TESTARE:
const builder = new ComandaBuilder()
    .setClient("Ion Ionescu")
    .setTotal(450);

// builder.build(); 
// EROARE DE COMPILARE: The 'this' context of type is not assignable! Lipsește 'adresaLivrare'!

const comandaGata = builder.setAdresa("Strada Florilor nr. 10").build(); // VALID!

2. The Result Pattern (Tratarea Erorilor ca Valori în loc de throw)

Aruncarea de excepții prin throw new Error() are un mare defect în TypeScript: tipul returnat de funcție nu include excepția aruncată! O funcție cu semnătura function salveaza(): Promise<User> ascunde complet faptul că poate crăpa cu NetworkError sau ValidationError.

Modelul Result (inspirat din limbaje precum Rust sau F#) transformă eșecul într-o valoare returnată explicită:

Implementarea Result<T, E> cu Discriminated Unions
export type Result<T, E = Error> =
    | { readonly ok: true; readonly valoare: T }
    | { readonly ok: false; readonly eroare: E };

export function Succes<T>(valoare: T): Result<T, never> {
    return { ok: true, valoare };
}

export function Esec<E>(eroare: E): Result<never, E> {
    return { ok: false, eroare };
}

// Funcție de business care returnează un Result garantat:
function imparteNumere(a: number, b: number): Result<number, string> {
    if (b === 0) return Esec("Împărțirea la zero nu este permisă!");
    return Succes(a / b);
}

const calcul = imparteNumere(10, 2);
if (calcul.ok) {
    console.log(`Rezultat: ${calcul.valoare}`); // 'calcul.valoare' este de tip number
} else {
    console.error(`Eroare: ${calcul.eroare}`);  // 'calcul.eroare' este de tip string
}

3. Repository Pattern Type-Safe

Pentru a decupla logica de business de o bază de date anume (Prisma, Drizzle sau TypeORM), definiți o interfață generică abstractă cu operațiuni CRUD puternic tipate:

interface BazaEntitate { id: string | number; }

interface IRepository<T extends BazaEntitate> {
    gasesteDupaId(id: T['id']): Promise<T | null>;
    gasesteToti(filtru?: Partial<T>): Promise<T[]>;
    salveaza(entitate: Omit<T, 'id'>): Promise<T>;
    sterge(id: T['id']): Promise<boolean>;
}

Această arhitectură permite înlocuirea imediată a stocării reale cu o implementare în memorie (In-Memory Repository) pentru teste unitare ultra-rapide și deterministe.