Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 19 additions & 7 deletions docs/getting-started/application-startup.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,40 @@ const kernel = new Kernel({
servicesYamlPath: 'config/container/services.yaml',
});

kernel.loadEnvironmentVariables(process.env.NODE_ENV || 'local');

await kernel.dependencyInjection({
containerBuild: process.env.CONTAINER_BUILD === 'true',
containerBuild: kernel.environment.NODE_ENV !== 'production',
});
kernel.registerRoutes(GetUserByIdRoute);
kernel.registerConsumers(...applicationConsumers);
kernel.registerSchedulers(...recurringSchedulers);

const server = new ExpressKernelServer({ kernel, port: 3000 });
const server = new ExpressKernelServer({
kernel,
port: Number(kernel.environment.PORT ?? 3000),
});
kernel.registerShutdownHook(() => server.close());

await server.run();
await kernel.runConsumers();
await kernel.runSchedulers();

kernel.logger.info('Application running on port 3000');
kernel.logger.info(
`Application running on port ${kernel.environment.PORT ?? 3000}`,
);
```

`CONTAINER_BUILD=true` regenerates `config/container/services.yaml`. Without it,
the generated YAML is loaded.
`loadEnvironmentVariables()` loads `.env.local` by default when `NODE_ENV` is
not set. Passing `test` loads `.env.test`; passing an empty string loads `.env`.

`containerBuild: true` regenerates `config/container/services.yaml`. Production
runtimes should usually load the generated YAML instead of rebuilding from
`src`.

`new Kernel(...)` configures defaults for the runtime. `kernel.dependencyInjection(...)`
can override the DI-specific values for a particular boot.
`new Kernel(...)` configures defaults for the runtime.
`kernel.dependencyInjection(...)` can override the DI-specific values for a
particular boot.

Call `kernel.shutdown()` from your process signal handlers to stop consumers,
stop schedulers, close servers, flush logs and release connections registered
Expand Down
7 changes: 4 additions & 3 deletions docs/getting-started/package-map.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@ Bootstrap code chooses adapters.
| Area | Import | Purpose |
| ---------------------- | ----------------------------------------- | ------------------------------------------------------------------------ |
| Kernel runtime | `@haskou/ddd-kernel` | Registers consumers, routes, schedulers, runtimes and shutdown hooks. |
| Environment variables | `@haskou/ddd-kernel` | Loads `.env.<environment>` files and exposes `Kernel.environment`. |
| Dependency injection | `@haskou/ddd-kernel/dependency-injection` | Wraps `node-dependency-injection` and container YAML generation/loading. |
| Lifecycle | `@haskou/ddd-kernel/lifecycle` | Runtime and initializer contracts. |
| Kernel logger contract | `@haskou/ddd-kernel/contracts/kernel` | `KernelLogger` interface. |

`node-dependency-injection` and `fs-extra` are package dependencies because the
core DI implementation uses them directly. Applications do not install them
separately.
`dotenv`, `node-dependency-injection` and `fs-extra` are package dependencies
because the core environment and DI implementations use them directly.
Applications do not install them separately.

## Contracts And Domain

Expand Down
4 changes: 3 additions & 1 deletion docs/reference/dependency-injection.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ Most applications should configure DI through the kernel:
```ts
const kernel = new Kernel();

kernel.loadEnvironmentVariables();

await kernel.dependencyInjection({
containerBuild: process.env.NODE_ENV !== 'production',
containerBuild: kernel.environment.NODE_ENV !== 'production',
});
```

Expand Down
47 changes: 47 additions & 0 deletions docs/reference/kernel.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,53 @@ const kernel = new Kernel({
`servicesYamlPath` and `sourceDirectory` can also be passed directly to
`kernel.dependencyInjection(...)`; the method-level value wins.

## Environment Variables

Load environment variables before dependency injection or adapters read their
`process.env` fallbacks:

```ts
const kernel = new Kernel();

kernel.loadEnvironmentVariables(process.env.NODE_ENV || 'local');
```

The default environment is `process.env.NODE_ENV || 'local'`, so calling
`kernel.loadEnvironmentVariables()` loads `.env.local` when `NODE_ENV` is not
set.

| Call | Loaded file |
| ------------------------------------------------------------------- | ---------------------------------- |
| `kernel.loadEnvironmentVariables()` | `.env.${NODE_ENV}` or `.env.local` |
| `kernel.loadEnvironmentVariables('test')` | `.env.test` |
| `kernel.loadEnvironmentVariables('')` | `.env` |
| `kernel.loadEnvironmentVariables('local', { path: 'config/.env' })` | `config/.env` |

Existing variables are not overwritten unless `override` is enabled:

```ts
kernel.loadEnvironmentVariables('local', { override: true });
```

`Kernel.environment` and `kernel.environment` expose `process.env` through one
kernel-owned access point:

```ts
const port = Number(Kernel.environment.HTTP_PORT ?? 3000);
```

Applications can type known variables by augmenting `NodeJS.ProcessEnv`:

```ts
declare global {
namespace NodeJS {
interface ProcessEnv {
HTTP_PORT?: string;
}
}
}
```

## Dependency Injection

Most applications call this once during startup:
Expand Down
12 changes: 9 additions & 3 deletions example/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,14 @@ const kernel = new Kernel({
sourceDirectory: path.resolve(rootDirectory, 'src'),
});

// Environment variables are loaded before DI/adapters read their process.env
// fallbacks. With NODE_ENV unset this loads .env.local.
kernel.loadEnvironmentVariables(process.env.NODE_ENV || 'local');

// In development the container is rebuilt from default exports under src/.
// In production it can reuse the generated services.yaml file.
await kernel.dependencyInjection({
containerBuild: process.env.NODE_ENV !== 'production',
containerBuild: kernel.environment.NODE_ENV !== 'production',
});

// Consumer middleware is registered once and runs around every domain event
Expand All @@ -60,7 +64,7 @@ const requestLoggerMiddleware: RequestHandler = (request, response, next) => {

const server = new ExpressKernelServer({
kernel,
port: Number(process.env.PORT ?? 3000),
port: Number(kernel.environment.PORT ?? 3000),
});

// The Express adapter stays optional. HTTP middleware, hooks and error handlers
Expand All @@ -84,7 +88,9 @@ server
kernel.registerShutdownHook(() => server.close());

await server.run();
kernel.logger.info(`Application running on port ${process.env.PORT ?? 3000}`);
kernel.logger.info(
`Application running on port ${kernel.environment.PORT ?? 3000}`,
);

// Delegate OS signals to the kernel so consumers, schedulers, servers and logs
// are stopped consistently.
Expand Down
13 changes: 11 additions & 2 deletions example/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,11 @@
integrity sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==

"@haskou/ddd-kernel@file:..":
version "0.1.1"
version "1.0.3"
dependencies:
dotenv "^16.6.1"
fs-extra "^11.3.5"
node-dependency-injection "3.2.6"

"@haskou/value-objects@^2.12.0":
version "2.12.0"
Expand Down Expand Up @@ -592,6 +596,11 @@ dir-glob@^3.0.1:
dependencies:
path-type "^4.0.0"

dotenv@^16.6.1:
version "16.6.1"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.6.1.tgz#773f0e69527a8315c7285d5ee73c4459d20a8020"
integrity sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==

dunder-proto@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a"
Expand Down Expand Up @@ -840,7 +849,7 @@ fresh@~0.5.2:
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==

fs-extra@11.3.5:
fs-extra@11.3.5, fs-extra@^11.3.5:
version "11.3.5"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.3.5.tgz#07a44eff40bea53e719909a532f91a23bf0769ff"
integrity sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@
],
"license": "MIT",
"dependencies": {
"dotenv": "^16.6.1",
"fs-extra": "^11.3.5",
"node-dependency-injection": "3.2.6"
},
Expand Down
40 changes: 40 additions & 0 deletions src/Kernel.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import dotenv, { type DotenvConfigOutput } from 'dotenv';
import path from 'node:path';

import type { Consumer } from './adapters/pubsub/index.js';
Expand All @@ -11,13 +12,15 @@ import type { ServiceClass } from './infrastructure/dependency-injection/index.j
import type { Initializer, Runtime } from './infrastructure/lifecycle/index.js';
import type { Scheduler } from './infrastructure/scheduler/index.js';
import type { KernelDependencyInjectionOptions } from './kernel/KernelDependencyInjectionOptions.js';
import type { KernelEnvironmentVariablesOptions } from './kernel/KernelEnvironmentVariablesOptions.js';
import type { KernelOptions } from './kernel/KernelOptions.js';
import type { ShutdownCandidate } from './kernel/ShutdownCandidate.js';

import { ConsoleKernelLogger } from './adapters/kernel/index.js';
import { DependencyInjection } from './infrastructure/dependency-injection/index.js';

export type { KernelDependencyInjectionOptions } from './kernel/KernelDependencyInjectionOptions.js';
export type { KernelEnvironmentVariablesOptions } from './kernel/KernelEnvironmentVariablesOptions.js';
export type { KernelOptions } from './kernel/KernelOptions.js';

export class Kernel {
Expand Down Expand Up @@ -59,6 +62,10 @@ export class Kernel {
return Kernel.getActiveKernel().di;
}

public static get environment(): NodeJS.ProcessEnv {
return process.env;
}

public static get logger(): KernelLogger {
return Kernel.getActiveKernel().logger;
}
Expand Down Expand Up @@ -91,6 +98,28 @@ export class Kernel {
return Kernel.state.activeKernel;
}

private static getEnvironmentVariablesPath(
environment: string,
options: KernelEnvironmentVariablesOptions,
): string {
return path.resolve(
Kernel.rootDirectory,
options.path ?? (environment ? `.env.${environment}` : '.env'),
);
}

public static loadEnvironmentVariables(
environment?: string,
options: KernelEnvironmentVariablesOptions = {},
): DotenvConfigOutput {
const environmentName = environment ?? process.env.NODE_ENV ?? 'local';

return dotenv.config({
override: options.override,
path: Kernel.getEnvironmentVariablesPath(environmentName, options),
});
}

constructor(private readonly options: KernelOptions = {}) {
this.loggerInstance = options.logger ?? new ConsoleKernelLogger();
this.dependencyInjectionInstance = options.di;
Expand Down Expand Up @@ -159,6 +188,10 @@ export class Kernel {
return this.dependencyInjectionInstance;
}

public get environment(): NodeJS.ProcessEnv {
return Kernel.environment;
}

public get logger(): KernelLogger {
return this.loggerInstance;
}
Expand Down Expand Up @@ -203,6 +236,13 @@ export class Kernel {
Kernel.state.activeKernel = this;
}

public loadEnvironmentVariables(
environment?: string,
options: KernelEnvironmentVariablesOptions = {},
): DotenvConfigOutput {
return Kernel.loadEnvironmentVariables(environment, options);
}

public getRoutes(): ServiceClass<Route>[] {
return this.routes;
}
Expand Down
4 changes: 4 additions & 0 deletions src/kernel/KernelEnvironmentVariablesOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface KernelEnvironmentVariablesOptions {
readonly override?: boolean;
readonly path?: string;
}
Loading
Loading