generated from Evyweb/node-script-template
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
15 changed files
with
241 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,66 @@ | ||
# Project name | ||
# Draft of a simple IOC container in Typescript | ||
|
||
## Introduction | ||
This is just a draft for an attempt to create a simple IOC (Inversion of Control) container in Typescript. The idea is to create a simple container that can be used to register and resolve dependencies working with functions (no class) and without reflect metadata. | ||
|
||
## How to use | ||
|
||
### List the dependencies | ||
Create a symbol for each dependency you want to register. It will be used to identify the dependency. | ||
|
||
```typescript | ||
export const DI = { | ||
HELLO_WORLD: Symbol('HELLO_WORLD'), | ||
DEP1: Symbol('dep1'), | ||
DEP2: Symbol('dep2'), | ||
MY_SERVICE: Symbol('MyService'), | ||
MY_USE_CASE: Symbol('MyUseCase'), | ||
LOGGER: Symbol('LOGGER'), | ||
}; | ||
``` | ||
|
||
### Register the dependencies | ||
|
||
```typescript | ||
import { DI } from './di'; | ||
|
||
const container: Container = createContainer(); | ||
|
||
// You can register primitives | ||
container.bind(DI.DEP1).toValue('dependency1'); | ||
container.bind(DI.DEP2).toValue(42); | ||
|
||
// You can register functions without dependencies | ||
container.bind(DI.HELLO_WORLD).toFunction(sayHelloWorld); | ||
|
||
// You can register a factory so dep1 and dep2 will be injected | ||
container.bind(DI.MY_SERVICE).toFactory(() => { | ||
return MyService({ | ||
dep1: container.get<string>(DI.DEP1), | ||
dep2: container.get<number>(DI.DEP2) | ||
}); | ||
}); | ||
|
||
// You can register a factory so myService will be injected | ||
container.bind(DI.MY_USE_CASE).toFactory(() => { | ||
return MyUseCase({ | ||
myService: container.get<MyService>(DI.MY_SERVICE) | ||
}); | ||
}); | ||
``` | ||
|
||
### Resolve the dependencies | ||
|
||
```typescript | ||
import { DI } from './di'; | ||
|
||
// Call the container to resolve the dependencies | ||
const myUseCase = container.get<MyUseCaseInterface>(DI.MY_USE_CASE); | ||
|
||
myUseCase.execute(); | ||
``` | ||
|
||
Code used in the examples can be found in the specs folder. | ||
|
||
This is just a draft and it is not ready for production. | ||
Can be improved in many ways. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
import {MyService} from "./MyService"; | ||
import {MyServiceInterface} from "./MyServiceInterface"; | ||
import {SayHelloType} from "./SayHelloType"; | ||
import {sayHelloWorld} from "./sayHelloWorld"; | ||
import {MyUseCase} from "./MyUseCase"; | ||
import {MyUseCaseInterface} from "./MyUseCaseInterface"; | ||
import {LoggerInterface} from "./LoggerInterface"; | ||
import {DI} from "./DI"; | ||
import {Container, createContainer} from "../src/container"; | ||
|
||
describe('Container', () => { | ||
|
||
let container: Container; | ||
|
||
beforeEach(() => { | ||
container = createContainer(); | ||
}); | ||
|
||
describe('When the function is registered using a symbol', () => { | ||
it('should return the associated function', () => { | ||
// Arrange | ||
container.bind(DI.HELLO_WORLD).toFunction(sayHelloWorld); | ||
|
||
// Act | ||
const sayHello = container.get<SayHelloType>(DI.HELLO_WORLD); | ||
|
||
// Assert | ||
expect(sayHello()).toBe('hello world'); | ||
}); | ||
|
||
it('should resolve all its dependencies using the factory', () => { | ||
// Arrange | ||
container.bind(DI.DEP1).toValue('dependency1'); | ||
container.bind(DI.DEP2).toValue(42); | ||
|
||
container.bind(DI.MY_SERVICE).toFactory(() => { | ||
return MyService({ | ||
dep1: container.get<string>(DI.DEP1), | ||
dep2: container.get<number>(DI.DEP2) | ||
}); | ||
}); | ||
|
||
// Act | ||
const myService = container.get<MyServiceInterface>(DI.MY_SERVICE); | ||
|
||
// Assert | ||
expect(myService.runTask()).toBe('Executing with dep1: dependency1 and dep2: 42'); | ||
}); | ||
|
||
describe('When the dependency has dependencies', () => { | ||
it('should resolve all its dependencies using the factory', () => { | ||
// Arrange | ||
container.bind(DI.DEP1).toValue('dependency1'); | ||
container.bind(DI.DEP2).toValue(42); | ||
container.bind(DI.HELLO_WORLD).toFunction(sayHelloWorld); | ||
|
||
container.bind(DI.MY_SERVICE).toFactory(() => { | ||
return MyService({ | ||
dep1: container.get<string>(DI.DEP1), | ||
dep2: container.get<number>(DI.DEP2) | ||
}); | ||
}); | ||
|
||
const fakeLogger: LoggerInterface = { | ||
log: vi.fn() | ||
} | ||
container.bind(DI.LOGGER).toValue(fakeLogger); | ||
|
||
container.bind(DI.MY_USE_CASE).toFactory(() => { | ||
return MyUseCase({ | ||
myService: container.get<MyServiceInterface>(DI.MY_SERVICE), | ||
logger: container.get<LoggerInterface>(DI.LOGGER), | ||
sayHello: container.get<SayHelloType>(DI.HELLO_WORLD) | ||
}); | ||
}); | ||
|
||
// Act | ||
const myUseCase = container.get<MyUseCaseInterface>(DI.MY_USE_CASE); | ||
|
||
// Assert | ||
expect(myUseCase.execute()).toBe('Executing with dep1: dependency1 and dep2: 42'); | ||
expect(fakeLogger.log).toHaveBeenCalledTimes(2); | ||
expect(fakeLogger.log).toHaveBeenCalledWith('Executing with dep1: dependency1 and dep2: 42'); | ||
expect(fakeLogger.log).toHaveBeenCalledWith('hello world'); | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
export const DI = { | ||
HELLO_WORLD: Symbol('HELLO_WORLD'), | ||
DEP1: Symbol('dep1'), | ||
DEP2: Symbol('dep2'), | ||
MY_SERVICE: Symbol('MyService'), | ||
MY_USE_CASE: Symbol('MyUseCase'), | ||
LOGGER: Symbol('LOGGER'), | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export interface LoggerInterface { | ||
log: (message: string) => void; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import {MyServiceInterface} from "./MyServiceInterface"; | ||
|
||
interface Dependencies { | ||
dep1: string, | ||
dep2: number | ||
} | ||
|
||
export const MyService = ({ dep1, dep2 }: Dependencies): MyServiceInterface => ({ | ||
runTask() { | ||
return `Executing with dep1: ${dep1} and dep2: ${dep2}`; | ||
} | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export interface MyServiceInterface { | ||
runTask: () => string; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import {MyServiceInterface} from "./MyServiceInterface"; | ||
import {MyUseCaseInterface} from "./MyUseCaseInterface"; | ||
import {LoggerInterface} from "./LoggerInterface"; | ||
import {SayHelloType} from "./SayHelloType"; | ||
|
||
interface Dependencies { | ||
myService: MyServiceInterface, | ||
logger: LoggerInterface, | ||
sayHello: SayHelloType | ||
} | ||
|
||
export function MyUseCase({myService, logger, sayHello}: Dependencies): MyUseCaseInterface { | ||
return { | ||
execute() { | ||
const message = myService.runTask(); | ||
logger.log(message); | ||
logger.log(sayHello()); | ||
return message; | ||
} | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export interface MyUseCaseInterface { | ||
execute: () => string; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export type SayHelloType = () => string; |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import {SayHelloType} from "./SayHelloType"; | ||
|
||
export const sayHelloWorld: SayHelloType = () => 'hello world'; |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
export interface Container { | ||
bind(key: symbol): { | ||
toValue: (value: any) => void; | ||
toFunction: (fn: CallableFunction) => void; | ||
toFactory: (factory: CallableFunction) => void; | ||
}; | ||
|
||
get<T>(key: symbol): T; | ||
} | ||
|
||
export function createContainer(): Container { | ||
const functionsOrValues = new Map<symbol, any>(); | ||
const factories = new Map<symbol, CallableFunction>(); | ||
|
||
function bind(key: symbol) { | ||
return { | ||
toValue: (value: any) => functionsOrValues.set(key, value), | ||
toFunction: (fn: CallableFunction) => functionsOrValues.set(key, fn), | ||
toFactory: (factory: CallableFunction) => factories.set(key, factory) | ||
}; | ||
} | ||
|
||
function get<T>(key: symbol): T { | ||
if (factories.has(key)) { | ||
const factory = factories.get(key)!; | ||
return factory(); | ||
} | ||
return functionsOrValues.get(key); | ||
} | ||
|
||
return {bind, get}; | ||
} |
This file was deleted.
Oops, something went wrong.