Backend run mode
A backend application can be instantiated in different ways:
- Command line interface (CLI)
- Server-only instance in production
- Poller-only instance in production
- A dev instance that both serves and also polls
I call these “backend run modes”, and I like to make them a first-class concept with an enum.
import { Enum, EnumValue } from "@kejistan/enum";
export const BackendRunMode = Enum({
// Different flavors of NODE_ENV = 'development'
DEV_INSTANCE: "DEV_INSTANCE",
DEV_INSTANCE_INSECURE_BLACK_BOX: "DEV_INSTANCE_INSECURE_BLACK_BOX",
DEV_REPL: "DEV_REPL",
DEV_CLI: "DEV_CLI",
// Different flavors of NODE_ENV = 'production'
PROD_INSTANCE_SERVER: "PROD_INSTANCE_SERVER",
PROD_INSTANCE_POLLER: "PROD_INSTANCE_POLLER",
PROD_TUNNEL_CLI: "PROD_TUNNEL_CLI",
PROD_TUNNEL_REPL_READ_ONLY: "PROD_TUNNEL_REPL_READ_ONLY",
// Different flavors of NODE_ENV = 'test'
TEST_UNIT: "TEST_UNIT",
TEST_BLACK_BOX: "TEST_BLACK_BOX",
TEST_INTEGRATION: "TEST_INTEGRATION",
});
export type BackendRunMode = EnumValue<typeof BackendRunMode>;
Configuring the backend run mode
The backend run mode is set by the “runner” file that gets executed first. So for example if a CLI command is initiated like this:
yarn ts-node src/app/app-cli.runner.ts
Then as the first line in that file we’ll have an import like this:
import "src/common/backend-run-mode/configure-backend-run-mode-cli";
Which imports a file that sets the run mode.
import { BackendRunMode } from "src/common/backend-run-mode/backend-run-mode";
global.BACKEND_RUN_MODE = BackendRunMode.CLI_COMMAND;
You may need to adjust .prettierrc.rs
to ensure that import particular import comes first:
importOrder: [
'^src/common/(.*)/configure-(.*)',
// …
],
In other cases the run mode might be a function of both (a) the runner file and also (b) some environment variable set by the compute environment.
Conditional module loading
Some modules are only relevant in specific run modes.
Poller
When running in poller mode, there’s no need to load the CLI commands, REST controllers, or GraphQL resolvers. We only need the pollers and the core business logic they depend on.
See the atomic backend modules post for explanation of these submodules.
Server
Similarly, there’s no need to load the pollers when running in server mode:
CLI
To further limit dependencies, separate CLI runners can limit to just what’s needed for a particular CLI:
yarn ts-node src/platform/user/cli/user-cli.runner.ts
PlantUML
Poller
@startuml
skinparam map {
BackgroundColor white
}
skinparam componentStyle rectangle
component "backend running as poller" {
component poller #LightBlue
package user {
component [cli] as cli_user
component [gql] as gql_user
component [poll] as poll_user #LightBlue
component [core] as core_user #LightBlue
component [rest] as rest_user
}
package tenant {
component [cli] as cli_tenant
component [gql] as gql_tenant
component [poll] as poll_tenant #LightBlue
component [core] as core_tenant #LightBlue
component [rest] as rest_tenant
}
}
poller --> poll_user
poller --> poll_tenant
cli_user --> core_user
gql_user --> core_user
poll_user --> core_user
rest_user --> core_user
cli_tenant --> core_tenant
poll_tenant --> core_tenant
rest_tenant --> core_tenant
gql_tenant --> core_tenant
core_user --> core_tenant
@enduml
Server
@startuml
skinparam map {
BackgroundColor white
}
skinparam componentStyle rectangle
component "backend running as server" {
component server #LightBlue
package user {
component [rest] as rest_user #LightBlue
component [gql] as gql_user #LightBlue
component [cli] as cli_user
component [poll] as poll_user
component [core] as core_user #LightBlue
}
package tenant {
component [rest] as rest_tenant #LightBlue
component [gql] as gql_tenant #LightBlue
component [cli] as cli_tenant
component [poll] as poll_tenant
component [core] as core_tenant #LightBlue
}
}
server --> rest_tenant
server --> gql_tenant
server --> rest_user
server --> gql_user
cli_user --> core_user
gql_user --> core_user
poll_user --> core_user
rest_user --> core_user
gql_tenant --> core_tenant
rest_tenant --> core_tenant
cli_tenant --> core_tenant
poll_tenant --> core_tenant
core_user --> core_tenant
@enduml
CLI
@startuml
skinparam map {
BackgroundColor white
}
skinparam componentStyle rectangle
component "backend running as CLI from user module" {
package user {
component [rest] as rest_user
component [gql] as gql_user
component [cli] as cli_user #LightBlue
component [poll] as poll_user
component [core] as core_user #LightBlue
}
package tenant {
component [rest] as rest_tenant
component [gql] as gql_tenant
component [cli] as cli_tenant
component [poll] as poll_tenant
component [core] as core_tenant #LightBlue
}
}
rest_user --> core_user
cli_user --> core_user
gql_user --> core_user
poll_user --> core_user
rest_tenant --> core_tenant
cli_tenant --> core_tenant
gql_tenant --> core_tenant
poll_tenant --> core_tenant
core_user --> core_tenant
@enduml