jservice

0.3.10 • Public • Published

JService

Build Status codecov

A small and powerful pure javascript DI container that is non-opinionated, no automatic dependency resolution, and with scoping such as Singleton, Scoped and Transient. Can optionally integrate with any Node web frameworks that supports middleware, like Express, Koa, Fasify, etc.

Manual Resolution

Does NOT require to configure which dependencies to resolve automatically into the function or class, but instead, only a single provider object is passed (that can resolve services). It's like manual resolution, but with cleaner parameter signature.

Integrates with Web Frameworks

Directly infuse web frameworks with dependency injection without changing the middleware's function signature.

Scoping

  • Singleton - services are the same across providers
  • Scoped - services are the same within the provider but different on another instance of provider
  • Transient - services are always different even within the same provider

Declaration & Configuration

Isolate dependency declaration and configuration into a single file. Giving more control and ensure no implicit dependencies are leaked in.

Install

npm install jservice

Basic Usage

Using the core container without a web framework.

var JService = require('jservice')
var DbService = require('./services/database')
var UserService = require('./services/user')
 
// Create a container
var jservice = JService.create()
// or var jservice = new JService()
 
// Add add services to container
function registry(services) {
  // DbService is a class or function constructor
  services.singleton(DbService, 'db')
  services.transient(UserService, 'user')
}
 
// Build services
jservice.build(registry)
 
// Resolve service via provider
var db = jservice.provider.service('db')
db.Books.find(123).then(book => console.log(book))

Basic Integration Usage

Easily integrate with any web framework using middleware.

var JService = require('jservice')
var express = require('express')
var app = express()
var registry = require('./registry')
var { IncomingMessage } = require('http')
 
// Create container with registry function
var jservice = JService.create(registry)
 
// Infuse jservice to express through middleware
app.use(jservice.init(IncomingMessage.prototype))
 
// Signup endpoint
app.post('/signup', (req, res) => {
 
  // Get the user service we registered in registry
  var user = req.service('user')
 
  // Using the service to create new user
  user.createNewUser({
    name: req.body.name,
    password: req.body.password
  }).then(() => res.json({ success: 'Ok' }))
 
})
 
// Start server when jservice is ready
jservice.start().then(() => app.listen(3000))

Creating Services

A service can be a class, function, or concrete object. You basically don't need to do or set anything in the service. However, there are some options you can take, like explicitly putting the name and hook to startup events.

Basic ES5 service user.js, with no name and hooks using functional programming (vanilla service).

function User(provider) {
  // Injecting database service
  var db = provider.service('db')
  // Exposed methods
  return {
    createNewUser: function(data) {
      return db.User.create(data)
    }
  }
}
module.exports = User

Basic ES6 service database.js with name and hooks using class.

import mongoose from 'mongoose'
 
class Database {
  constructor(provider, config) {
    this.URL = config.url
    this.User = mongoose.model(
      'User',
      { name: String, password: String }
    )
  }
  connect(provider) {
    return mongoose.connect(this.URL || 'mongodb://localhost:27017/test')
  }
}

Registering Services

Create a file registry.js and add all the services.

var Database = require('./services/database')
var User = require('./services/user')
// more service ...
 
module.exports = services => {
 
  services.singleton(Database, 'db')
  services.transient(User, 'user')
  services.scoped(Foo)
  services.add(Bar) // alias for singleton
 
  // We can also add a service multiple times but need to set different name
  services.add(Config, 'user-config')
  services.add(Config, 'database-config')
  // more services...
 
  // Some services can be configured, like this
  services.configure(Database, provider => {
    // Here we get host and port from config
    var { url } = provider.get('database-config')
    // Or, you can also set it here
    // var url = '...'
    return { url }
  })
}

There are several ways to add registry

// 1st option
var jservice = new JService(registry)
// 2nd option
var jservice = JService.create(registry)
// 3rd option
var jservice = new JService()
jservice.build(registry)
// 4th option
var jservice = JService.create()
jservice.build(registry)
jservice.build(moreRegistry)

Service Scoping & Samples

In sample folder, you'll find various web framework integration samples and scope testing.

Notice the output ids generated when getting services. Same ids means the same instance.

  • Singleton services have same id across providers
  • Scoped services have same id within the request but different on subsequent request
  • Transient services are always different (new instance)
GLOBAL SERVICE PROVIDER
Singleton |  plgcGPuZu
 
SCOPED PROVIDER A (1st request)
Singleton |  plgcGPuZu
Singleton |  plgcGPuZu
Scoped    |  8YG0POVDjR
Scoped    |  8YG0POVDjR
Transient |  ZAoy3AJooc
Transient |  ok1MP0cYir
 
SCOPED PROVIDER B (2nd request)
Singleton |  plgcGPuZu
Singleton |  plgcGPuZu
Scoped    |  TQPz6T_Suk
Scoped    |  TQPz6T_Suk
Transient |  sPsB8hnC3U
Transient |  8iz9mz8bC9

You check out the sample and run it yourself.

git clone <repo>
npm install
npm run sample

API

Container (JService)

const JService = require('jservice')
const container = JService.create() // Or "new JService()"

Properties

.collection Collection - service storage (add or disable services) .provider Provider - service injector (get services) .isReady Boolean - when start method is done

Methods

.build(registry)

Build up the container. Requires registry function that accepts the services Collection.

container.build(services => {
  // `services` is instance of Collection
  services.add(MyService)
  // ...
})

.init(prototype, opt = {}) | returns middleware

Binds JService to any web frameworks that supports middleware. Check out the samples folder for supported frameworks and sample usage.

const express = require('express')
const { IncomingMessage } = require('http')
const app = express()
app.use(jservice.init(IncomingMessage.prototype))
jservice.start().then(() => app.listen(3000))

.start()

Starts the container, triggering all services that hooks to start event.

.createContainer() | returns new Container

Creates a sub-container that inherits all services from parent container.

const container = JService.create()
const subContainer = container.createContainer()

.createProvider() | return new Provider

Create a scoped provider.

Collection

.singleton(service, name = null, deps = null)

Alias: .add(...)

Adds a singleton service to collection. Can omit name (string) if the service contains static property service = <name>. Can also filter or customize dependencies available for the service using deps (function)

services.singleton(ZooService, 'zoo', provider => {
  const customProvider = provider.create({
    other: provider.get('other'),
    custom: { foo: 'bar' }
  })
  return ZooService(customProvider)
})
 
// Custom 3rd party vanilla services
servics.add(express, 'express', provider => {
  return express()
})

.transient(service, name = null, deps = null)

Adds a transient mode service.

.scoped(service, name = null, deps = null)

Adds a scoped mode service.

.configure(name, config)

Configure a service. The name can be either string or function service, to identify which service to configure. The config is a function.

services.configure(Database, provider => {
  // Get data from config service
  const config = provider.get('config')
  return {
    host: config.host,
    port: config.port
  }
})

.enable(name, yes = true)

Enable/disable a service.

Provider

.create(deps)

Creates a sub (filtered) provider that only contains dependencies in deps

.service(name)

Alias: .get(name)

Strictly get a service. Throws error if service is not found.

.serviceOrNull(name)

Alias: .getOrNull(name)

Softly get a service. Will NOT throw error, but instead returns null.

Mocking

const { mock } = require('jservice')
// Import service to mock
const User = require('./user.js')
function FakeDatabase() { }
 
// Mock a provider with fake dependencies
const { provider } = mock(
  [FakeDatabase, 'db']
  // more ...
)
 
// Create instance of user service to test
new User(provider)

License

MIT License Copyright (c) 2019 RhaldKhein

Package Sidebar

Install

npm i jservice

Weekly Downloads

27

Version

0.3.10

License

MIT

Unpacked Size

38.7 kB

Total Files

15

Last publish

Collaborators

  • rhaldkhein