Как постигнахме по-добро разделяне на проблемите с модела на фабричните функции

Ако работите с NodeJS, вероятно сте използвали require(), за да получите всички зависимости за вашия код. Това може да ви попречи по време на тестване на единици, но proxyquire го опрости отново.

И така, защо искате да прочетете повече за управлението на зависимости? Ами ако ви кажа, че може да използвате require за нещо, за което не е предназначен? Освен това, като възприемете този модел на инжектиране на зависимости, ще се насладите на повече модулен код, по-добри начини за справяне с изключения от зависимости и по-лесно време за писане на модулни тестове.

Зависимост със състояние

Представете си, че изграждате бекенд сървър със слоеве контролер/услуга/модел. Моделите се нуждаят от връзка с база данни, за да четат или записват в нея. Вашата връзка с база данни може да бъде в модул като:

const dbDriver = require('some-db-driver');
const connection = dbDriver.init('db-uri');
module.exports = connection;

След това във вашите моделни модули вие просто:

const dbConnection = require('./dbConnection');
module.exports.find = dbConnection.table('cupcakes').find;

Всичко работи идеално. хубаво! Но на следващия ден библиотеката на db драйверите пусна нова версия и ви каза, че driver.init() е много опасно || скъп || бавна работа и трябва да се извиква само веднъж, когато вашият сървър работи.

Сега, как гарантирате, че dbDriver.init се извиква само веднъж, когато имате 7 моделни модула, всички имат require('./dbConnection')?

Състояние на модула, управлявано от Require Cache

След като се заровихте в публикации за „как работи require(), вече знаете, че require използва някакъв механизъм за кеширане. Така че, когато require един и същ файл 7 пъти, той не зарежда сляпо файла отново и отново от диска или driver.init() 7 пъти. По принцип вашият модул dbConnection ще се държи повече или по-малко като инициализиран сингълтон, споделян от модулите на вашия модел, които всички го изискват.

Сингълтонът, поддържан от require.cache, може да е достатъчен за много случаи на употреба. Има „капани“, които трябва да се избягват. И трябва да се доверите, че другата част от приложението няма да обезсили кеша в даден момент. Официалният документ гласи „че“:

Множеството извиквания към require('foo') може да не доведат до многократно изпълнение на кода на модула.

Така че няма гаранция, че множество require('foo') винаги ще водят до един и същ обект. Някои хора като мен смятат, че require не е предназначен за управление на състояния на модули.

Централизирано управление на зависимостите

Вместо да разчитаме на require.cache, ние управляваме нашата зависимост по по-ръчен и централизиран начин. Всички наши моделни модули изглеждат така:

const statelessDeps = require('lib');
module.exports = (dbConnection, otherStatefulDeps) => {
  const self = {
    find() {
      return dbConnection.table('cupcakes').find();
    }
  };
  return self;
}

И имаме нужда от място, където да предадем всички тези зависимости в тези модули. Може да е server.js :

const dbDriver = require('some-db-driver');
const CupcakeModel = require('./models/cupcake');
const CupcakeService = require('./services/cupcake');
const CupcakeController = require('./controllers/cupcake');
try {
  const connection = dbDriver.init('db-uri');
  const cupcakeModel = CupcakeModel(dbConnection);
  const cupcakeService = CupcakeService(cupcakeModel, trashbinModel):
  app.use('/cupcakes', CupcakeController(cupcakeService, orderService));
} catch (error) { //catch driver initialization error here}

В този пример ние управляваме зависимостите на модулите ръчно и изрично. Когато внедрявате тези индивидуални модули, винаги ще приемате, че зависимостите със състояние се предават към вас чрез модела на фабричната функция. Вече няма нужда да се притеснявате дали вашите зависимости са в правилното състояние!

Заключение

Ние използваме модел на фабрична функция, за да инжектираме зависимости със състояние на всички наши модули за задната част. Помага ни да разделим притесненията за управление на състояния на зависимости спрямо използването им за внедряване на бизнес логика.

Един конкретен пример за такава полза е, че централизираното управление на зависимости улеснява обработката на изключения. Вместо да се дублират кодове за обработка на грешки за зависимости, където се използват, сега всичко е на едно и само едно място.

Бонусна точка при използването на фабричен шаблон е, че тестването на единици на тези модули е толкова лесно, колкото правенето на някои обикновени стари JS обекти като фалшиви зависимости и предаването им. Няма нужда да намирате алтернатива на proxyquire, когато мигрирате към import!

Моделът на фабричната функция е голяма тема. Можете да прочетете повече за това тук.