NestJS: mantienilo semplice, stupido

Feb 25 2023
Un'alternativa per l'utilizzo del modulo di configurazione
Immagina, stamattina inizia un nuovo collega e tutto ciò che deve fare è utilizzare una variabile di ambiente nella tua applicazione Nest. Felicemente accetta di svolgere il compito, semplicemente estrarre una variabile tramite processo.

Immagina, stamattina inizia un nuovo collega e tutto ciò che deve fare è utilizzare una variabile di ambiente nella tua applicazione Nest. Felicemente accetta di svolgere il compito, semplicemente estrarre una variabile tramite process.env. Fino a quando qualcuno non glielo dice, non accediamo process.envdirettamente, ma utilizziamo invece il modulo di configurazione .

Negli ultimi anni ho guidato più team sui progetti NestJS e la maggior parte di loro ha semplicemente seguito i documenti NestJS, non hanno iniziato con il perché .

"Perchè facciamo ciò che facciamo?"

Ma ho affrontato le parti buone, cattive e persino brutte della struttura. E ho iniziato a chiedermi "Nest è con noi o contro di noi?" . E sorprendentemente ho concluso che alcune parti di Nest stanno effettivamente lavorando contro di noi.

Affrontare il nostro nemico

Non fraintendermi, amo ancora Nest. Ma in realtà sono abbastanza contento che la parte di configurazione di Nest non venga fornita immediatamente. In effetti, @nestjs/config è un modulo principale di Nest, che puoi installare separatamente, quindi solo se ne hai bisogno. E la parte divertente è che, in quasi tutti i progetti su cui ho lavorato, il modulo di configurazione è stato implementato per i seguenti motivi:

  • Sembra che il modulo di configurazione sia la strada da percorrere all'interno di Nest.
  • C'è abbastanza documentazione disponibile ( e chiaramente necessaria ).
  • Segue la stessa struttura supponente.
  • Si potrebbe estenderlo con la convalida ( una best practice che ho visto molto o meno )

Cosa manca al modulo di configurazione

In 1 parola, semplicità . L'accesso a una variabile di ambiente è una delle attività più comuni durante la giornata e quindi dovrebbe essere il più semplice possibile. Quindi il modulo di configurazione dovrebbe funzionare prima di noi, non contro di noi.

Per fare un esempio, l'accesso alla proprietà foonel appnamespace ci costringe a implementare qualcosa di simile al seguente.

// app.config.ts
const appConfig = register('app', () => ({
  foo: process.env.APP_FOO
}))

// app.module.ts
ConfigModule.forRoot({
  load: [
     appConfig,
   ],
  }),
]);

// MyService.ts
class MyService {
 constructor(
   private configService: ConfigService 
 )

 myMethod(){
   // now we can finally get the env var
   const foo = this.configService.get<string>('app.foo')
 }
}

Partiamo dal presupposto che la risposta sarà un string, ma lo sapremo solo in fase di esecuzione. Non possiamo semplicemente usare il nostro editor o IDE per verificare se app.fooesiste effettivamente. Un errore di battitura può essere fatto in una frazione di secondo, con il risultato di minuti (o ore) di debug. E la dolorosa conclusione è che quello che volevamo davvero era accedere alla app.fooproprietà. Ma siamo finiti con il cosiddetto problema gorilla-banana-giungla .

“… Perché il problema con i linguaggi orientati agli oggetti è che hanno tutto questo ambiente implicito che si portano dietro. Volevi una banana ma quello che hai ottenuto è stato un gorilla con in mano la banana e l'intera giungla. “

— Joe Armstrong, creatore del linguaggio di programmazione Erlang

Nest ha introdotto una sorta di soluzione alternativa per poter verificare il tipo di una proprietà in fase di esecuzione, passando un oggetto opzioni.

const foo = this.configService.get<string>('app.foo', { infer: true })

  • che miriamo a una proprietà che esiste realmente;
  • quel linting e l'evidenziazione del codice ecc. nel nostro IDE funzionano e basta;
  • che il tipo di proprietà è dedotto per riferimento ;
  • e che possiamo usare la variabile d'ambiente al di fuori dell'ambito di Nest.

Come ingegnere, dovremmo sempre fare un passo indietro e pensare se stiamo ancora implementando l' acronimo Keep it simple, stupid . E come ora vediamo, il modulo di configurazione infrange chiaramente questa legge.

Anche se sembrerei pessimista, se non introdurrei un altro approccio. Uno più conveniente. Un approccio che sarebbe comunque supponente, ma che affronta anche i problemi menzionati in precedenza. Quindi saluta il mio piccolo amico :

export const appConfig = {
  foo: process.env.APP_FOO
}

// and consume it elsewhere...
const foo = appConfig.foo

E anche in applicazioni più grandi potremmo implementare qualcosa di simile. Tuttavia, sarebbe utile introdurre una configdirectory separata in una struttura di directory simile a questa:

├── src
│   ├── foo-module
│   │   ├── foo.controller.ts
│   │   ├── foo.service.ts
│   │   └── foo.module.ts
│   │
│   └── config
│       ├── appConfig.ts
│       └── dbConfig.ts
│   
├── package.json
└── nest-cli.json

Come ingegnere dovremmo sempre iniziare con il perché . Dovremmo sforzarci di pensare fuori dagli schemi piuttosto che continuare a pensarci dentro. Anche se ciò significa che lasceremmo parzialmente un framework supponente come NestJS.

Per concludere con una famosa citazione: “Facciamo di nuovo grande l'accesso all'ambiente vars” . Concentrati sulle banane, non sui gorilla nelle giungle.