Я пришел к выводу, что в этом случае код не может быть полностью отсоединен.Существует определенная связь между бизнес-логикой и реализацией репозитория по определению задачи.
Однако, чтобы упростить дальнейшее обслуживание кода, я использовал следующую архитектуру (в псевдокоде):
Основные интерфейсы:
// Main repository interface
interface OrdersRepositoryInterface {
store(order: Order): bool;
// …other methods
}
// An interface of a factory that will be used to create repository instances with different configurations (different underlying storages, for example)
interface OrdersRepositoryFactoryInterface {
createRepository(configuration: OrdersRepositoryConfigurationInterface): OrdersRepositoryInterface;
}
// An interface of a container that will create different instances of repositories based on specified brand
// An implementation of this interface is the coupling point between business logic and data persistence logic
interface OrdersRepositoryContainerInterface {
get(brand: Brand): OrdersRepositoryInterface;
}
Реализация фабрики репозитория (тесно связанная с самим репозиторием, можно избежать, если в самом интерфейсе был указан конструктор репозитория, но я лично считаю, что это плохая практика):
class OrdersRepositoryImplementation implements OrdersRepositoryInterface {
constructor(configuration: OrdersRepositoryConfigurationInterface) {
// …
}
store(order: Order): bool {
// …
}
}
class OrdersRepositoryFactory implements OrdersRepositoryFactoryInterface {
createRepository(configuration: OrdersRepositoryConfigurationInterface): OrdersRepositoryInterface {
return new OrdersRepositoryImplementation(configuration);
}
}
Реализация контейнера репозитория:
class OrdersRepositoryContainer implements OrdersRepositoryContainerInterface {
get(brand: Brand): OrdersRepositoryInterface {
var factory = IoC.resolve(OrdersRepositoryFactoryInterface);
var configuration1 = …;
var configuration2 = …;
if (brand.slug === "brand1") {
return factory.createRepository(configuration1);
} else {
return factory.createRepository(configuration2);
}
}
}
Привязка IoC-контейнера (если контейнер поддерживает это, вероятно, лучше связывать классы, а не экземпляры, поскольку автоматическое внедрение зависимости может использоваться в конструкторах этих реализаций):
IoC.bindInstance(OrdersRepositoryFactoryInterface, new OrdersRepositoryFactory());
IoC.bindInstance(OrdersRepositoryContainerInterface, new OrdersRepositoryContainer());
И последний, но не менее важный, код обработки заказа:
var order = …;
var repositoryContainer = IoC.resolve(OrdersRepositoryContainerInterface);
var repository = repositoryContainer.get(order.brand);
repository.store(order);
Эта архитектура позволит легко заменить логику разрешения репозитория.Например, репозитории для всех брендов могут быть объединены в будущем, и в этом случае должна быть заменена только реализация OrderRepositoryContainerInterface.Но, тем не менее, OrdersRepositoryContainer по-прежнему связан с реализацией репозиториев, поскольку он должен знать, как и где получить их конфигурацию.
Я отмечу этот ответ как принятый, но я с готовностью его поменяю, если кто-нибудь предложитлучшая идея.