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
18 changes: 6 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ An example is "auth". All code related to authentication and users, could go int

## Systemic Architecture

After an extensive development career looking for the best methods, and trying to keep up with the rate of change, eventually one will stumble upon the reality that nearly systems follows predictable structures. One of these structures follows the natural course of how a parts within a system works together, as well as where the complicated and easy parts are. The trouble in most systems is that complexity and known troublesome code concepts are embedded throughout the application rather than putting them in well-defined areas that can be understood and maintained.
After an extensive development career looking for the best methods, and trying to keep up with the rate of change, eventually one will stumble upon the reality that nearly systems follow predictable structures. One of these structures follows the natural course of how a parts within a system works together, as well as where the complicated and easy parts are. The trouble in most systems is that complexity and known troublesome code concepts are embedded throughout the application rather than putting them in well-defined areas that can be understood and maintained.

A well organized system tends to be defined in "layers", where parts of the system are designed to speak to certain other parts of the system.

Expand All @@ -50,24 +50,18 @@ When a system is designed from the start using distinct layers, it makes it real

There are four primary layers in any system, and these are the layers that Node In Layers comes out of the box with. They are the following:

- Dependencies
- Globals
- Services
- Features
- Entries

## Dependencies - The Special Layer
## Globals - The Everywhere Layer

Node In Layers is a dependency injection framework as well as an opinionated framework that heavily suggests how code should be organized and initiated. There are dependencies that exist throughout a system that every single layer uses. Configurations, environment variables, etc.
Node In Layers is a dependency injection framework as well as an opinionated framework that heavily suggests how code should be organized and initiated. There are items that exist throughout a system that every single layer uses. Configurations, environment variables, etc.

Every application has dependencies that are used throughout the application. Here are some common ones:
Unlike every other layer in Node In Layers, this "globals" layer is a special "layer." This layer is made widely available throughout the system, and has no namespaces. (Therefore be careful of collisions).

- Configurations
- Logger
- Environmental Constants

Unlike every other layer in Node In Layers, this "dependencies" layer is a special "layer." This layer is made widely available throughout the system, and has no namespaces. (Therefore be careful of collisions).

Simply create a constructor function in an apps `dependencies.ts` file, and it will create the dependencies at the beginning of runtime, and then distributed up the app stack.
Simply create a constructor function in an apps `globals.ts` file, and it will create the dependencies at the beginning of runtime, and then distributed up the app stack.

### Services - The Outside World Communicators

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@node-in-layers/core",
"type": "module",
"version": "1.1.2",
"version": "1.1.3",
"description": "The core library for the Node In Layers rapid web development framework.",
"main": "index.js",
"scripts": {
Expand Down
12 changes: 6 additions & 6 deletions src/entries.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import omit from 'lodash/omit.js'
import * as dependenciesApp from './dependencies.js'
import * as globalsApp from './globals.js'
import * as layersApp from './layers.js'
import { Config, CoreNamespace, NodeDependencies } from './types.js'

Expand All @@ -8,23 +8,23 @@ const loadSystem = async <TConfig extends Config = Config>(args: {
config?: TConfig
nodeOverrides?: NodeDependencies
}) => {
const depServices = dependenciesApp.services.create({
const globalServices = globalsApp.services.create({
environment: args.environment,
workingDirectory: process.cwd(),
nodeOverrides: args.nodeOverrides,
})
const depFeatures = dependenciesApp.features.create({
const globalFeatures = globalsApp.features.create({
services: {
[dependenciesApp.name]: depServices,
[globalsApp.name]: globalServices,
},
})
const dependencies = await depFeatures.loadDependencies(
const globals = await globalFeatures.loadGlobals(
args.config || args.environment
)

const layersServices = layersApp.services.create()
const layersFeatures = layersApp.features.create({
...dependencies,
...globals,
services: {
[layersApp.name]: layersServices,
},
Expand Down
49 changes: 22 additions & 27 deletions src/dependencies.ts → src/globals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,40 +15,40 @@ import {
} from './types.js'
import { memoizeValue } from './utils.js'

const name = CoreNamespace.dependencies
const name = CoreNamespace.globals

type DependenciesServicesProps = Readonly<{
type GlobalsServicesProps = Readonly<{
environment: string
workingDirectory: string
nodeOverrides?: Partial<NodeDependencies>
}>

type DependenciesServices<TConfig extends Config> = Readonly<{
type GlobalsServices<TConfig extends Config> = Readonly<{
loadConfig: () => Promise<TConfig>
configureLogging: (config: TConfig) => RootLogger
getConstants: () => {
workingDirectory: string
environment: string
}
getNodeServices: () => NodeDependencies
getDependencies: (
commonDependencies: CommonContext<TConfig>,
getGlobals: (
commonGlobals: CommonContext<TConfig>,
app: App
) => Promise<Record<string, any>>
}>

type DependenciesFeatures<TConfig extends Config> = Readonly<{
loadDependencies: <TDependencies extends Record<string, any> = object>(
type GlobalsFeatures<TConfig extends Config> = Readonly<{
loadGlobals: <TGlobals extends Record<string, any> = object>(
environmentOrConfig: string | TConfig
) => Promise<CommonContext<TConfig> & TDependencies>
) => Promise<CommonContext<TConfig> & TGlobals>
}>

const services = {
create: <TConfig extends Config>({
environment,
workingDirectory,
nodeOverrides,
}: DependenciesServicesProps): DependenciesServices<TConfig> => {
}: GlobalsServicesProps): GlobalsServices<TConfig> => {
const useFullLogFormat = () => {
const originalFactory = log.methodFactory
// eslint-disable-next-line functional/immutable-data
Expand Down Expand Up @@ -170,12 +170,9 @@ const services = {
)
}

const getDependencies = (
commonDependencies: CommonContext<TConfig>,
app: App
) => {
if (app.dependencies) {
return app.dependencies.create(commonDependencies)
const getGlobals = (commonGlobals: CommonContext<TConfig>, app: App) => {
if (app.globals) {
return app.globals.create(commonGlobals)
}
return Promise.resolve({})
}
Expand All @@ -185,7 +182,7 @@ const services = {
getConstants,
configureLogging,
getNodeServices,
getDependencies,
getGlobals,
}
},
}
Expand All @@ -195,39 +192,37 @@ const features = {
services,
}: {
services: {
[CoreNamespace.dependencies]: DependenciesServices<TConfig>
[CoreNamespace.globals]: GlobalsServices<TConfig>
}
}): DependenciesFeatures<TConfig> => {
}): GlobalsFeatures<TConfig> => {
const ourServices = get(services, name)

const loadDependencies = async <TDependencies extends object>(
const loadGlobals = async <TGlobals extends object>(
environmentOrConfig: string | TConfig
) => {
const config: TConfig = await (isConfig(environmentOrConfig)
? environmentOrConfig
: ourServices.loadConfig())
validateConfig(config)

const commonDependencies = {
const commonGlobals = {
config,
log: ourServices.configureLogging(config),
node: ourServices.getNodeServices(),
constants: ourServices.getConstants(),
}
const dependencies: TDependencies = await config[
CoreNamespace.root
].apps.reduce(
const globals: TGlobals = await config[CoreNamespace.root].apps.reduce(
async (accP, app) => {
const acc = await accP
const dep = await ourServices.getDependencies(commonDependencies, app)
const dep = await ourServices.getGlobals(commonGlobals, app)
return merge(acc, dep)
},
Promise.resolve({} as TDependencies)
Promise.resolve({} as TGlobals)
)
return merge(commonDependencies, dependencies)
return merge(commonGlobals, globals)
}
return {
loadDependencies,
loadGlobals,
}
},
}
Expand Down
4 changes: 2 additions & 2 deletions src/layers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,10 @@ const features = {
const loadLayers = () => {
const layersInOrder = context.config[CoreNamespace.root].layerOrder
const antiLayers = getLayersUnavailable(layersInOrder)
const ignoreLayers = [CoreNamespace.layers, CoreNamespace.dependencies]
const ignoreLayers = [CoreNamespace.layers, CoreNamespace.globals]
.map(l => `services.${l}`)
.concat(
[CoreNamespace.layers, CoreNamespace.dependencies].map(
[CoreNamespace.layers, CoreNamespace.globals].map(
l => `features.${l}`
)
)
Expand Down
39 changes: 19 additions & 20 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,12 @@ type RootLogger = Readonly<{

enum CoreNamespace {
root = '@node-in-layers/core',
dependencies = '@node-in-layers/core/dependencies',
globals = '@node-in-layers/core/globals',
layers = '@node-in-layers/core/layers',
}

type Config = Readonly<{
systemName: string
environment: string
[CoreNamespace.root]: {
logLevel: LogLevelNames
Expand All @@ -71,10 +72,10 @@ type Config = Readonly<{

type AppLayer<
TConfig extends Config = Config,
TDependencies extends object = object,
TContext extends object = object,
TLayer extends object = object,
> = Readonly<{
create: (dependencies: LayerContext<TConfig, TDependencies>) => TLayer
create: (dependencies: LayerContext<TConfig, TContext>) => TLayer
}>

type NodeDependencies = Readonly<{
Expand All @@ -93,67 +94,65 @@ type CommonContext<TConfig extends Config = Config> = Readonly<{

type LayerContext<
TConfig extends Config = Config,
TDependencies extends object = object,
> = CommonContext<TConfig> & TDependencies
TContext extends object = object,
> = CommonContext<TConfig> & TContext

type ServicesContext<
TConfig extends Config = Config,
TServices extends object = object,
TDependencies extends object = object,
TContext extends object = object,
> = LayerContext<
TConfig,
{
services: TServices
} & TDependencies
} & TContext
>

type ServicesLayerFactory<
TConfig extends Config = Config,
TServices extends object = object,
TDependencies extends object = object,
TContext extends object = object,
TLayer extends object = object,
> = Readonly<{
create: (
context: ServicesContext<TConfig, TServices, TDependencies>
) => TLayer
create: (context: ServicesContext<TConfig, TServices, TContext>) => TLayer
}>

type DependenciesLayer<
type GlobalsLayer<
TConfig extends Config = Config,
TDependencies extends object = object,
TGlobals extends object = object,
> = Readonly<{
create: (context: CommonContext<TConfig>) => Promise<TDependencies>
create: (context: CommonContext<TConfig>) => Promise<TGlobals>
}>

type FeaturesContext<
TConfig extends Config = Config,
TServices extends object = object,
TFeatures extends object = object,
TDependencies extends object = object,
TGlobals extends object = object,
> = LayerContext<
TConfig,
{
services: TServices
features: TFeatures
} & TDependencies
} & TGlobals
>

type FeaturesLayerFactory<
TConfig extends Config = Config,
TDependencies extends object = object,
TContext extends object = object,
TServices extends object = object,
TFeatures extends object = object,
TLayer extends object = object,
> = Readonly<{
create: (
context: FeaturesContext<TConfig, TServices, TFeatures, TDependencies>
context: FeaturesContext<TConfig, TServices, TFeatures, TContext>
) => TLayer
}>

type System<
TConfig extends Config = Config,
TFeatures extends object = object,
TServices extends object = object,
TFeatures extends object = object,
> = CommonContext<TConfig> & {
services: TServices
features: TFeatures
Expand All @@ -163,7 +162,7 @@ type App = Readonly<{
name: string
services?: AppLayer<Config, any>
features?: AppLayer<Config, any>
dependencies?: DependenciesLayer<Config, any>
globals?: GlobalsLayer<Config, any>
}>

export {
Expand Down
3 changes: 3 additions & 0 deletions test/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ const deleteUnitTestConfig = () => {

const validConfig1 = () => ({
environment: 'unit-test',
systemName: 'nil-core',
[CoreNamespace.root]: {
apps: [
{
Expand All @@ -71,6 +72,7 @@ const validConfig1 = () => ({

const validConfig2 = () => ({
environment: 'unit-test',
systemName: 'nil-core',
[CoreNamespace.root]: {
apps: [
{
Expand All @@ -97,6 +99,7 @@ const validConfig2 = () => ({

const validConfig3 = () => ({
environment: 'unit-test',
systemName: 'nil-core',
[CoreNamespace.root]: {
apps: [
{
Expand Down
6 changes: 2 additions & 4 deletions test/src/dependencies.test.ts → test/src/globals.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@ import * as chai from 'chai'
import asPromised from 'chai-as-promised'
import {
createMockFs,
validConfig3,
validConfig2,
validConfig1,
deleteUnitTestConfig,
writeUnitTestConfig,
} from '../mocks'
import { services, features } from '../../src/dependencies'
import { services, features } from '../../src/globals'

chai.use(asPromised)

Expand All @@ -31,7 +29,7 @@ const _setup = () => {
}
}

describe('/src/dependencies.ts', () => {
describe('/src/globals.ts', () => {
describe('#services.create()', () => {})
describe('#features.create()', () => {
describe('#loadLayers()', () => {})
Expand Down
Loading