1

我正在使用 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
    }
}
4

1 回答 1

1

这是 Doug Schmidt(完全公开了我目前的博士顾问)写的关于上下文对象模式的论文。

https://www.dre.vanderbilt.edu/~schmidt/PDF/Context-Object-Pattern.pdf

正如 dbugger 所说,在您的 api 类中构建一个返回适当“配置”对象的工厂是一种非常干净的方法。但是,如果您知道所讨论论文的“上下文”(是的,重载用法),它主要用于中间件。有多层上下文变化的地方。请注意,在“实施”部分下,它建议使用策略模式来了解如何将每一层的“上下文信息”添加到“上下文对象”。

我会推荐一个类似的方法。每个“存储容器”都有与之相关的不同策略。因此,每个“驱动程序”都有自己的策略实现。班级。该策略将从工厂获得,然后根据需要使用。(如何设计你的 Strats ......最好的方法(我猜)是让你的“驱动程序策略”对每种驱动程序类型都是通用的,然后在新资源出现/分配策略对象时适当地配置它)

但据我现在所知(除非我读错了你的问题),这将只有 2 个“层”,“上下文对象”会知道,“其余服务器”和“存储端点”。如果我错了,那就这样吧......但只有 2 层,您可以像思考“上下文模式”一样使用“策略模式”,并避免单例/上下文“反模式”的问题'。(您“可以”拥有一个上下文对象,其中包含要使用的驱动程序的策略,然后是该驱动程序的“配置”......这不会很疯狂,并且可能很适合您的动态 HTTP 配置。)

策略工厂类也不必“必须”是单例/具有静态工厂方法。即使使用 DI 进行测试,我也已经制作了以前作为对象的工厂。不同的方法总是需要权衡取舍,但我发现在我遇到的几乎所有情况下,更好的测试都是值得的。

于 2015-12-24T03:46:48.623 回答