存储和加载 REST 服务器避免全局状态的配置(即单例、上下文和依赖注入)
Storing and loading configuration for REST server avoding global state (i.e. singleton vs. context vs. dependency injection)
我正在使用 tomcat 在 Java 中开发一个架构,我遇到了一个我认为非常普遍的情况,但是在阅读了 Whosebug 中的几个 questions/answers 之后,我找不到确定的答案。我的架构有一个 REST API(运行 on tomcat)接收一个或多个文件及其关联的元数据并将它们写入存储。存储层的配置与 REST API 服务器具有 1-1 的关系,因此直观的方法是编写一个 Singleton 来保存该配置。
显然我知道由于全局状态和模拟单例的困难,单例带来了可测试性问题。我也考虑过使用 Context 模式,但我不相信 Context 模式适用于这种情况,我担心我最终会使用 "Context anti-pattern" 来编码。
让我为您提供更多有关我所写内容的背景知识。该架构由以下组件组成:
向 REST 发送请求的客户端 API 上传或检索 "preservation objects",或者简单地说,PO(文件 + 元数据)在 JSON 或 XML 格式.
高级 REST API 接收来自客户端的请求并将数据存储在存储层中。
一个存储层,可能包含 OpenStack Swift 容器、磁带库和文件系统的组合。这些 "storage containers"(为简单起见,我称其为文件系统容器)中的每一个在我的体系结构中都称为端点。存储层显然不在RESTAPI所在的同一台服务器上。
端点的配置是通过 REST API(例如 POST /configEndpoint)完成的,因此管理用户可以通过 HTTP 调用注册新端点、编辑或删除现有端点。虽然我只使用 OpenStack Swift 端点实现了架构,但我预计每个端点的信息至少包含一个 IP 地址、某种形式的身份验证信息和一个驱动程序名称,例如"the Swift driver"、"the LTFS driver"等(这样当新的存储技术到来时,只要有人为它编写驱动程序,它们就可以很容易地集成到我的架构中)。
我的问题是:如何以可测试、可重用和优雅的方式存储和加载配置?我什至不会考虑将配置对象传递给实现 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
}
}
这是 Doug Schmidt(完全公开了我目前的博士生导师)写的关于上下文对象模式的论文。
https://www.dre.vanderbilt.edu/~schmidt/PDF/Context-Object-Pattern.pdf
正如 dbugger 所述,在您的 api class 中构建一个工厂 returns 适当的 'configuration' 对象是一种非常简洁的方法。但是如果你知道所讨论的论文的'context'(是的,重载用法),它主要用于中间件。有多层上下文变化的地方。请注意,在 'implementation' 部分下,它建议使用 Strategy Pattern 来了解如何将每个图层的 'context information' 添加到 'context object'。
我会推荐类似的方法。每个 'storage container' 都有与之相关的不同策略。因此,每个 "driver" 都有自己的策略含义。 class。该策略将从工厂获得,然后根据需要使用。 (如何设计你的 Strats ......最好的方法(我猜)是让你的 'driver strat' 对每种驱动程序类型都是通用的,然后适当地配置它作为新资源 arise/the strat 对象是已分配)
但据我现在所知(除非我读错了你的问题),这只有 2 'layers' 'context object' 会知道,'rest server(s)' 和 'storage endpoints'。如果我弄错了,那就这样吧......但是只有 2 层,你可以像你想的那样使用 'strategy pattern' 'context pattern',并避免 singletons/Context 的问题'anti-pattern'。 (您 'could' 有一个上下文对象,其中包含要使用哪个驱动程序的策略,然后是该驱动程序的 'configuration' ... HTTP 配置。)
策略工厂 Class 也不是 'have to' 静态工厂方法。我之前已经创建了作为对象的工厂,即使使用 D.I 也是如此。用于检测。不同的方法总是需要权衡取舍,但我发现在我 运行 遇到的几乎所有情况下,更好的测试都是值得的。
我正在使用 tomcat 在 Java 中开发一个架构,我遇到了一个我认为非常普遍的情况,但是在阅读了 Whosebug 中的几个 questions/answers 之后,我找不到确定的答案。我的架构有一个 REST API(运行 on tomcat)接收一个或多个文件及其关联的元数据并将它们写入存储。存储层的配置与 REST API 服务器具有 1-1 的关系,因此直观的方法是编写一个 Singleton 来保存该配置。
显然我知道由于全局状态和模拟单例的困难,单例带来了可测试性问题。我也考虑过使用 Context 模式,但我不相信 Context 模式适用于这种情况,我担心我最终会使用 "Context anti-pattern" 来编码。
让我为您提供更多有关我所写内容的背景知识。该架构由以下组件组成:
向 REST 发送请求的客户端 API 上传或检索 "preservation objects",或者简单地说,PO(文件 + 元数据)在 JSON 或 XML 格式.
高级 REST API 接收来自客户端的请求并将数据存储在存储层中。
一个存储层,可能包含 OpenStack Swift 容器、磁带库和文件系统的组合。这些 "storage containers"(为简单起见,我称其为文件系统容器)中的每一个在我的体系结构中都称为端点。存储层显然不在RESTAPI所在的同一台服务器上。
端点的配置是通过 REST API(例如 POST /configEndpoint)完成的,因此管理用户可以通过 HTTP 调用注册新端点、编辑或删除现有端点。虽然我只使用 OpenStack Swift 端点实现了架构,但我预计每个端点的信息至少包含一个 IP 地址、某种形式的身份验证信息和一个驱动程序名称,例如"the Swift driver"、"the LTFS driver"等(这样当新的存储技术到来时,只要有人为它编写驱动程序,它们就可以很容易地集成到我的架构中)。
我的问题是:如何以可测试、可重用和优雅的方式存储和加载配置?我什至不会考虑将配置对象传递给实现 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
}
}
这是 Doug Schmidt(完全公开了我目前的博士生导师)写的关于上下文对象模式的论文。
https://www.dre.vanderbilt.edu/~schmidt/PDF/Context-Object-Pattern.pdf
正如 dbugger 所述,在您的 api class 中构建一个工厂 returns 适当的 'configuration' 对象是一种非常简洁的方法。但是如果你知道所讨论的论文的'context'(是的,重载用法),它主要用于中间件。有多层上下文变化的地方。请注意,在 'implementation' 部分下,它建议使用 Strategy Pattern 来了解如何将每个图层的 'context information' 添加到 'context object'。
我会推荐类似的方法。每个 'storage container' 都有与之相关的不同策略。因此,每个 "driver" 都有自己的策略含义。 class。该策略将从工厂获得,然后根据需要使用。 (如何设计你的 Strats ......最好的方法(我猜)是让你的 'driver strat' 对每种驱动程序类型都是通用的,然后适当地配置它作为新资源 arise/the strat 对象是已分配)
但据我现在所知(除非我读错了你的问题),这只有 2 'layers' 'context object' 会知道,'rest server(s)' 和 'storage endpoints'。如果我弄错了,那就这样吧......但是只有 2 层,你可以像你想的那样使用 'strategy pattern' 'context pattern',并避免 singletons/Context 的问题'anti-pattern'。 (您 'could' 有一个上下文对象,其中包含要使用哪个驱动程序的策略,然后是该驱动程序的 'configuration' ... HTTP 配置。)
策略工厂 Class 也不是 'have to' 静态工厂方法。我之前已经创建了作为对象的工厂,即使使用 D.I 也是如此。用于检测。不同的方法总是需要权衡取舍,但我发现在我 运行 遇到的几乎所有情况下,更好的测试都是值得的。