Сохранение и загрузка конфигурации для глобального состояния avD-сервера REST (т. Е. Синглтон в зависимости от контекста и внедрение зависимости)
Я занимаюсь разработкой архитектуры на Java с использованием tomcat, и я столкнулся с ситуацией, которая, на мой взгляд, очень общая, и все же, прочитав несколько вопросов / ответов в Stackru, я не смог найти однозначного ответа. Моя архитектура имеет REST API (работающий на tomcat), который получает один или несколько файлов и связанные с ними метаданные и записывает их в хранилище. Конфигурация уровня хранения имеет отношение 1-1 к серверу API REST, и по этой причине интуитивно понятный подход заключается в написании Singleton для хранения этой конфигурации.
Очевидно, я знаю, что синглтоны приносят проблемы с тестируемостью из-за глобального состояния и трудностей, связанных с насмешками над синглетонами. Я также думал об использовании шаблона контекста, но я не уверен, что шаблон контекста применяется в этом случае, и я волнуюсь, что в итоге я буду кодировать, используя вместо этого "анти-шаблон контекста".
Позвольте мне дать вам дополнительную информацию о том, что я пишу. Архитектура состоит из следующих компонентов:
Клиенты, которые отправляют запросы в REST API, загружая или извлекая "объекты сохранения", или просто помещают PO (файлы + метаданные) в формате JSON или XML.
API REST высокого уровня, который получает запросы от клиентов и сохраняет данные на уровне хранилища.
Уровень хранения, который может содержать комбинацию контейнеров OpenStack Swift, ленточных библиотек и файловых систем. Каждый из этих "контейнеров хранения" (я называю контейнеры файловых систем для простоты) называется конечной точкой в моей архитектуре. Уровень хранилища, очевидно, не находится на том же сервере, где находится REST API.
Конфигурирование конечных точек выполняется через API REST (например, POST / configEndpoint), так что администратор может регистрировать новые конечные точки, редактировать или удалять существующие конечные точки через HTTP-вызовы. Хотя я реализовал архитектуру только с использованием конечной точки OpenStack Swift, я предполагаю, что информация для каждой конечной точки содержит, по крайней мере, IP-адрес, некоторую форму информации для аутентификации и имя драйвера, например, "драйвер Swift", "драйвер LTFS". и т. д. (чтобы при появлении новых технологий хранения их можно было легко интегрировать в мою архитектуру, если кто-то напишет драйвер для нее).
Моя проблема: как мне сохранить и загрузить конфигурацию тестируемым, многократно используемым и элегантным способом? Я даже не буду рассматривать передачу объекта конфигурации всем различным методам, которые реализуют вызовы REST API.
Несколько примеров вызовов API REST и того, где конфигурация вступает в игру:
// Retrieve a preservation object metadata (PO)
@GET
@Path("container/{containername}/{po}")
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public PreservationObjectInformation getPOMetadata(@PathParam("containername") String containerName, @PathParam("po") String poUUID) {
// STEP 1 - LOAD THE CONFIGURATION
// One of the following options:
// StorageContext.loadContext(containerName);
// Configuration.getInstance(containerName);
// Pass a configuration object as an argument of the getPOMetadata() method?
// Some sort of dependency injection
// STEP 2 - RETRIEVE THE METADATA FROM THE STORAGE
// Call the driver depending on the endpoint (JClouds if Swift, Java IO stream if file system, etc.)
// Pass poUUID as parameter
// STEP 3 - CONVERT JSON/XML TO OBJECT
// Unmarshall the file in JSON format
PreservationObjectInformation poi = unmarshall(data);
return poi;
}
// Delete a PO
@DELETE
@Path("container/{containername}/{po}")
public Response deletePO(@PathParam("containername") String containerName, @PathParam("po") String poName) throws IOException, URISyntaxException {
// STEP 1 - LOAD THE CONFIGURATION
// One of the following options:
// StorageContext.loadContext(containerName); // Context
// Configuration.getInstance(containerName); // Singleton
// Pass a configuration object as an argument of the getPOMetadata() method?
// Some sort of dependency injection
// STEP 2 - CONNECT TO THE STORAGE ENDPOINT
// Call the driver depending on the endpoint (JClouds if Swift, Java IO stream if file system, etc.)
// STEP 3 - DELETE THE FILE
return Response.ok().build();
}
// Submit a PO and its metadata
@POST
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Path("container/{containername}/{po}")
public Response submitPO(@PathParam("containername") String container, @PathParam("po") String poName, @FormDataParam("objectName") String objectName,
@FormDataParam("inputstream") InputStream inputStream) throws IOException, URISyntaxException {
// STEP 1 - LOAD THE CONFIGURATION
// One of the following options:
// StorageContext.loadContext(containerName);
// Configuration.getInstance(containerName);
// Pass a configuration object as an argument of the getPOMetadata() method?
// Some sort of dependency injection
// STEP 2 - WRITE THE DATA AND METADATA TO STORAGE
// Call the driver depending on the endpoint (JClouds if Swift, Java IO stream if file system, etc.)
return Response.created(new URI("container/" + container + "/" + poName))
.build();
}
** ОБНОВЛЕНИЕ № 1 - Моя реализация основана на комментарии @mawalker **
Найдите ниже мою реализацию, используя предложенный ответ. Фабрика создает конкретные стратегические объекты, которые реализуют действия хранения более низкого уровня. Объект контекста (который передается промежуточным программным обеспечением назад и вперед) содержит объект абстрактного типа (в данном случае интерфейс) StorageContainerStrategy (его реализация будет зависеть от типа хранилища в каждом конкретном случае во время выполнения).
public interface StorageContainerStrategy {
public void write();
public void read();
// other methods here
}
public class Context {
public StorageContainerStrategy strategy;
// other context information here...
}
public class StrategyFactory {
public static StorageContainerStrategy createStorageContainerStrategy(Container c) {
if(c.getEndpoint().isSwift())
return new SwiftStrategy();
else if(c.getEndpoint().isLtfs())
return new LtfsStrategy();
// etc.
return null;
}
}
public class SwiftStrategy implements StorageContainerStrategy {
@Override
public void write() {
// OpenStack Swift specific code
}
@Override
public void read() {
// OpenStack Swift specific code
}
}
public class LtfsStrategy implements StorageContainerStrategy {
@Override
public void write() {
// LTFS specific code
}
@Override
public void read() {
// LTFS specific code
}
}
1 ответ
Вот статья Дуга Шмидта (полностью раскрывающая мой нынешний доктор философии), написанная о шаблоне объекта контекста.
https://www.dre.vanderbilt.edu/~schmidt/PDF/Context-Object-Pattern.pdf
Как сказал dbugger, встроить фабрику в ваши классы API, которая возвращает соответствующий объект 'configuration', - довольно чистый способ сделать это. Но если вы знаете "контекст" (да, перегруженное использование) обсуждаемой статьи, это в основном для использования в промежуточном программном обеспечении. Где есть несколько уровней изменения контекста. И обратите внимание, что в разделе "реализация" он рекомендует использовать шаблон стратегии для того, как добавить "контекстную информацию" каждого слоя в "объект контекста".
Я бы порекомендовал аналогичный подход. Каждый "контейнер для хранения" будет иметь свою собственную стратегию. Таким образом, каждый "водитель" имеет свою собственную стратегию. учебный класс. Эта стратегия будет получена с завода, а затем использована по мере необходимости. (Как спроектировать ваши Strats... лучший способ (я предполагаю) состоит в том, чтобы сделать ваш 'драйверский страт' универсальным для каждого типа драйвера, а затем настроить его соответствующим образом по мере появления новых ресурсов / назначения объекта strat)
Но, насколько я могу сказать прямо сейчас (если я не читаю ваш вопрос неправильно), это будет иметь только 2 "слоя", о которых "объект контекста" будет знать, "остальные серверы" и " конечные точки хранения ". Если я ошибаюсь, то пусть так и будет... но только с двумя слоями, вы можете просто использовать "шаблон стратегии" так же, как вы думали "шаблон контекста", и избежать проблемы анти-шаблона "одиночные / контекстные" ". (Вы "можете" иметь объект контекста, который содержит стратегию для используемого драйвера, а затем "конфигурацию" для этого драйвера... которая не будет безумной и может хорошо соответствовать вашей динамической конфигурации HTTP.)
Фабричный класс (ы) Стратегии также не должен быть одноэлементным / иметь статические фабричные методы. Я сделал фабрики, которые раньше были объектами, просто отлично, даже с DI для тестирования. Всегда есть компромиссы между различными подходами, но я считаю, что лучшее тестирование оправдывает себя почти во всех случаях, с которыми я столкнулся.