我正在使用 tomcat 开发 Java 架构,我遇到了一种我认为非常通用的情况,但是,在阅读了 StackOverflow 中的几个问题/答案之后,我找不到明确的答案。我的架构有一个 REST API(在 tomcat 上运行),它接收一个或多个文件及其关联的元数据并将它们写入存储。存储层的配置与 REST API 服务器是 1-1 的关系,因此直观的方法是编写一个 Singleton 来保存该配置。
显然,我知道由于全局状态和模拟 Singleton 的困难,Singleton 会带来可测试性问题。我也想过使用上下文模式,但我不相信上下文模式适用于这种情况,我担心我最终会使用“上下文反模式”进行编码。
让我给你一些关于我所写内容的更多背景知识。该架构由以下组件组成:
向 REST API 发送请求的客户端以 JSON 或 XML 格式上传或检索“保存对象”,或者简单地说,PO(文件 + 元数据)。
接收来自客户端的请求并将数据存储在存储层中的高级 REST API。
一个存储层,可能包含 OpenStack Swift 容器、磁带库和文件系统的组合。这些“存储容器”中的每一个(为简单起见,我称其为文件系统容器)在我的体系结构中称为端点。存储层显然不在 REST API 所在的同一台服务器上。
端点的配置是通过 REST API 完成的(例如 POST /configEndpoint),因此管理用户可以通过 HTTP 调用注册新的端点、编辑或删除现有的端点。虽然我只使用 OpenStack Swift 端点实现了架构,但我预计每个端点的信息至少包含一个 IP 地址、某种形式的身份验证信息和一个驱动程序名称,例如“Swift 驱动程序”、“LTFS 驱动程序”等(这样当新的存储技术出现时,只要有人为其编写驱动程序,它们就可以轻松地集成到我的架构中)。
我的问题是:如何以可测试、可重用和优雅的方式存储和加载配置?我什至不会考虑将配置对象传递给实现 REST API 调用的所有各种方法。
REST API 调用和配置发挥作用的几个示例:
// 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
}
}