На этой странице

@zirion/ioc · Примеры

Запускаемые примеры

Полные примеры @zirion/ioc v3: HTTP-сервер Node.js, проверка времени жизни providers и повторные попытки асинхронных singleton-фабрик.

Обновлено · ·

Как запустить примеры

Это полные JavaScript-модули для Node.js 20 или новее. Фреймворк и компилятор TypeScript не нужны. Установите пакет в отдельной папке примеров:

npm init -y
npm install @zirion/ioc@^3.0.0

Сохраните каждый пример с указанным именем .mjs и запустите через node. Регистрации подробно разобраны в основном руководстве.

HTTP-сервер Node.js

Сохраните как server.mjs:

import { createServer } from 'node:http';
import { randomUUID } from 'node:crypto';
import { Container, InjectScope } from '@zirion/ioc';

class UserStore {
  users = [{ id: 1, name: 'Ada' }];
}

class RequestInfo {
  constructor(context) {
    this.context = context;
  }
}

class UserHandler {
  id = randomUUID();

  constructor(store, config, info) {
    this.store = store;
    this.config = config;
    this.info = info;
  }

  body() {
    return {
      requestId: this.info.context.requestId,
      handlerId: this.id,
      region: this.config.region,
      users: this.store.users,
    };
  }
}

const container = new Container()
  .add(UserStore, { scope: InjectScope.SINGLETON })
  .add('config', {
    scope: InjectScope.SINGLETON,
    valueFactory: async () => ({ region: 'eu-central-1' }),
  })
  .add(RequestInfo, { scope: InjectScope.REQUEST })
  .add(UserHandler, { inject: [UserStore, 'config', RequestInfo] });

const server = createServer(async (request, response) => {
  const context = { requestId: randomUUID() };

  try {
    let body;
    let status = 200;

    if (request.method === 'GET' && request.url === '/health') {
      const store = container.getOrFailSync(UserStore);
      body = { ok: true, users: store.users.length };
    } else if (request.method === 'GET' && request.url === '/users') {
      const handler = await container.getOrFail(UserHandler, context);
      body = handler.body();
    } else {
      status = 404;
      body = { error: 'Not found' };
    }

    response.writeHead(status, { 'content-type': 'application/json' });
    response.end(JSON.stringify(body));
  } catch (error) {
    console.error(error);
    response.writeHead(500, { 'content-type': 'application/json' });
    response.end(JSON.stringify({ error: 'Internal server error' }));
  }
});

server.listen(3000, '127.0.0.1', () => {
  console.log('http://127.0.0.1:3000/users');
});

process.once('SIGTERM', () => {
  server.close();
});

Запустите сервер и выполните два запроса:

node server.mjs
# В другом терминале:
curl http://127.0.0.1:3000/health
curl http://127.0.0.1:3000/users
curl http://127.0.0.1:3000/users

Хранилище и настройки зарегистрированы как singleton. Каждый HTTP-запрос получает свежий context и своё состояние. UserHandler transient: даже два разрешения внутри запроса создадут два обработчика с одним request-экземпляром RequestInfo.

/health использует синхронное разрешение синхронного графа хранилища. /users ожидает граф, поскольку настройки инициализируются асинхронно. В реальном приложении демонстрационную фабрику можно заменить фабрикой подключения. Закрывайте внешние клиенты в обработчике завершения приложения; finalize() их не закрывает.

Проверка времени жизни и повторного внедрения

Сохраните как lifetimes.mjs, затем запустите node lifetimes.mjs:

import assert from 'node:assert/strict';
import { Container, InjectScope } from '@zirion/ioc';

class Store {}
class RequestState {}
class Operation {}
class Pair {
  constructor(first, second) {
    this.first = first;
    this.second = second;
  }
}

const container = new Container()
  .add(Store, { scope: InjectScope.SINGLETON })
  .add(RequestState, { scope: InjectScope.REQUEST })
  .add(Operation)
  .add(Pair, { inject: [Operation, Operation] });

assert.equal(
  container.getOrFailSync(Store),
  container.getOrFailSync(Store),
);

const requestA = {};
const requestB = {};
assert.equal(
  container.getOrFailSync(RequestState, requestA),
  container.getOrFailSync(RequestState, requestA),
);
assert.notEqual(
  container.getOrFailSync(RequestState, requestA),
  container.getOrFailSync(RequestState, requestB),
);

const pair = container.getOrFailSync(Pair);
assert.notEqual(pair.first, pair.second);
assert.notEqual(pair, container.getOrFailSync(Pair));
console.log('All lifetime checks passed');

Один context переиспользует request-состояние, другой объект получает своё. Повторы transient-selectors в inject сохраняются и создают разные экземпляры. Если заменить Operation на singleton, повторное внедрение передаст один экземпляр.

Повторная попытка асинхронного singleton

Сохраните как retry.mjs, затем запустите node retry.mjs:

import assert from 'node:assert/strict';
import { Container, InjectScope } from '@zirion/ioc';

let attempts = 0;
const container = new Container().add('enabled', {
  scope: InjectScope.SINGLETON,
  valueFactory: async () => {
    attempts += 1;

    if (attempts === 1) {
      throw new Error('Temporary failure');
    }

    return false;
  },
});

await assert.rejects(container.getOrFail('enabled'), /Temporary failure/);
const [first, second] = await Promise.all([
  container.getOrFail('enabled'),
  container.getOrFail('enabled'),
]);
assert.equal(first, false);
assert.equal(second, false);
assert.equal(await container.getOrFail('enabled'), false);
assert.equal(attempts, 2);
console.log('Retry, concurrent resolution, and falsy caching verified');

Ошибка фабрики не кешируется. Следующие два потребителя разделяют одну успешную попытку, затем false нормально кешируется. Проверьте аналогичное поведение request-providers с общим context и классов, чей onInitialized() может завершиться ошибкой.

Дальше: миграция v2 → v3 и миграция с других контейнеров.

Наверх