Resource Manager

Copyright 2001,2020 Nikolas S. Boyd. All rights reserved. Nik Boyd

Resource Manager

Object Behavioral

Intent

Factor out resource lifecycle management into a separate class and focus client code on resource usage.

Motivation

Resource utilization usually has a consistent, often symmetrical, lifecycle. The lifecycle template for using a resource often includes the following steps:

  1. Obtain the resource.
  2. Prepare the resource for use (if needed).
  3. Use the resource.
  4. Restore the resource to its original state (if needed).
  5. Release the resource.

Steps 2 and 4 may or may not be present in a particular resource utilization lifecycle. However, whether they are present or not, the correct usage of a resource most often entails special code for ensuring that the lifecycle is correctly maintained. Some programming languages even provide special syntax for ensuring that a resource gets released after its use. For example,

try { … } catch (Exception ex) { … } finally { … }

Given such a code template, the typical resource utilization lifecycle will usually be coded in the following manner:

  1. try { obtain the resource, and prepare it for use (if needed)
  2. use the resource }
  3. catch (Exception) { and handle it appropriately }
  4. finally { restore the resource, and release it }

To ensure proper utilization of a resource, this code template will often be repeated in each place where a resource is used. Such repetition can be tedious and can often lead to coding errors, especially if such code gets copied, pasted, and then (all too often) revised. As the resource utilization lifecycle becomes more complex, and as the repetition of the code template becomes more pervasive throughout a program, the cost of repairing and maintaining such repetitious code increases geometrically.

As a typical example of this problem, consider database connection management in Java. When performing database operations using JDBC, the correct usage of a database connection invariably involves a try-catch-finally construction to ensure that the database connection is released after its usage, even after an exception occurs.

However, instead of repeating the connection management code pervasively throughout a Java program, it is possible to factor out the lifecycle management behavior into a ConnectionManager class. The ConnectionManager is responsible for ensuring the correct lifecycle for a database connection, especially obtaining the connection and releasing the connection, but also handling any exceptions that arise during the use of the connection.

connection.manager

The ConnectionManager obtains a Connection from a connection resource registry and provides the Connection to an instance of a ConnectionUsage class. When using JDBC 1.0, the resource registry will be the DriverManager. When using JDBC 2.0, the resource registry should be a DataSource.

In Java versions prior to 1.8, the ConnectionUsage class is a base class that is intended to be instantiated as an anonymous inner class by the Client. The Client overrides the default use() method to operate on the Connection supplied by the ConnectionManager.

With the advent of functional support in Java 8, we can simplify this somewhat by eliminating the anonymous inner class in favor of a functional pattern.

demo().connectUsing((c) -> … use the connection … );

Ultimately, the Client determines how to use the Connection, i.e., what operations are performed on the Connection. However, because the connection management code has been factored out into the ConnectionManager, the Client can focus solely on the database operations.

Thus, repetitions of the lifecycle management code are prevented. Overall, the resulting database access layer becomes simpler and easier to maintain.

Applicability

Use the Resource Manager pattern when

  • you have shared resources with a consistent (even symmetrical) usage lifecycle.
  • you want to have a single place (method or class) for managing resource lifecycles.
  • you want a uniform mechanism for handling exceptions when using the resources.
  • you want to simplify your resource access layer to focus on resource usage.

Structure

resource.manager

Participants

  • ResourceRegistry (DataSource)
    – reserves resources (e.g., Connections) for use

  • ResourceManager (ConnectionManager)
    – obtains a resource and
    – prepares the resource for use (if needed)
    – restores the resource after use (if needed) and
    – frees the resource

  • ResourceUsage (ConnectionUsage)
    – uses a resource

  • Resource (Connection)
    – provides access to specific services and/or data

Collaborations

  • The Client provides a ResourceUsage to a ResourceManager.
  • The ResourceManager obtains a Resource from a ResourceRegistry.
  • The ResourceManager prepares the Resource for use (if needed).
  • The ResourceManager supplies the Resource to the ResourceUsage.
  • The ResourceUsage uses the supplied Resource to perform some operation(s).
  • The ResourceManager restores the Resource to its original state (if needed).
  • The ResourceManager returns the Resource to the ResourceRegistry.

Consequences

The Resource Manager pattern has the following consequences:

  1. Improved maintainability. The code for managing the overall resource usage lifecycle is separated out into a single place. This dramatically reduces the number of places that need to be changed if the lifecycle needs to change.

  2. Uniform exception handling. Resource usage exceptions can be handled in a uniform manner. If special cases are needed, the framework can be extended to support plug-in exception handlers. The mechanism for supporting such a plug-ins will be similar to that which supports resource usage.

  3. Simpler code. The code for performing operations on a resource need not be concerned with resource management. Such client code can focus solely on the correct usage of the resource, especially if transactional semantics must be maintained.

Implementation

  1. Supportive Programming Environments. The Resource Manager pattern works best with a programming language that has a well defined exception handling mechanism. Fortunately, most modern programming languages include some kind of support for exception handling. Both Java and Smalltalk provide such mechanisms. The Java mechanism was outlined above in the Motivation section. Smalltalk provides a similar mechanism for ensuring that resources can be managed correctly. Any freestanding Smalltalk block can be guarded by an ensure: message with another block that will always be executed whether an exception occurs or not.

    [ “…” ] ensure: [ “…” ]

    The C++ try-catch construction does not include a finally clause. So, implementing the Resource Manager pattern in C++ requires some additional work. A couple of possible solutions are:

    1. Place the allocated resource in an instance of a smart pointer class declared as a local stack variable in the scope of the enclosing method. That way, when the stack unwinds, the smart pointer class will have an opportunity to restore and release the resource even if a resource usage exception occurs.

    2. Alternatively, the class that serves as a ResourceManager can catch all exceptions. Then, the ResourceManager can restore and release the allocated Resource, handle any exceptions it can, and re-throw any exceptions it can’t handle.

  2. Lambdas and Closures. Java lambdas are especially convenient for defining ResourceUsage behaviors. The insertSample() and fetchSample() methods in the ExampleMain class serve as representative examples. This is similar to how we would do this in Smalltalk. In Smalltalk, the ResourceManager accessUsing: method can simply take a single argument block as a parameter.

    resourceManager accessUsing: [ :resource | “…” ]

    C++ complicates the implementation of the Resource Manager pattern because it does not have any kind of direct closure mechanism like those found in Smalltalk and Java. However, it does support nested classes, which can be used to define ConcreteUsage classes close by to the location of their instantiation.

  3. Thread-Safety. The instantiation of each ResourceUsage will usually have a negligible impact on performance. However, if the kind of Resource being managed does not represent an external resource with a relatively large performance overhead (such as a database), it may improve performance if the usage can be pre-allocated, cached, and reused during each access. However, care must then be taken to ensure that each usage is thread-safe.

  4. Access Authentication. Some managed resources require authenticated access, e.g., database connections. It is relatively simple matter to extend the interface of a ResourceManager class to support authenticated access, either by supplying the authentication credentials during the instantiation of the ResourceManager, or by supplying the credentials as an additional parameter in the accessUsing() method. Better still will be to have a pool of such pre-authenticated resources from which one may be allocated (exclusively), used, and returned to the pool as part of the overall usage life-cycle.

  5. Access Variations. Depending on the context in which it is used, a ResourceManager class may need to provide multiple variations of the accessUsing() method. For example, if there are various ways to prepare a connection prior to its use, you may want to accommodate those variations with extra parameters. Also, if the programming language does not provide convenient access to outer scopes, you may need to pass some additional argument(s) into the ResourceManager, which may in turn need to pass the argument(s) in to the ResourceUsage class.

Sample Code

Recommended Uses

There are several components of the Java platform for which the Resource Manager pattern may be used:

  • Database Connections. As shown in the sample code, the Resource Manager pattern can be used to manage the lifecycles of database Connections obtained from JDBC DriverManagers or JDBC DataSources.

  • Message Service Connections. The Resource Manager pattern may also be applied to the lifecycle management of objects obtained from the JEE Java Message Service (JMS). Both JMS Connections and Sessions have lifecycles. So, it may be useful to create both a ConnectionManager and a SessionManager, or coordinate these using a single ResourceManager, depending on how much of their lifecycles it makes sense to package and factor out in a particular messaging application.

  • Enterprise Application Connections. The JEE Connector Architecture (JCA) defines a framework for integrating enterprise information systems (EIS). The lifecycle of Connections defined within this new framework may also be managed using the Resource Manager pattern.

Related Patterns

  • Template Method. The accessUsing() method in the ResourceManager class is an example of a kind of template method. The accessUsing() method contains the skeleton of an algorithm, i.e., the overall resource usage lifecycle. Rather than repeat this skeleton throughout the code of a resource access layer, the Resource Manager pattern factors out this template into the ResourceManager class.

  • Strategy. The ResourceUsage class uses a variation of the Strategy pattern. The ResourceUsage class defines an interface for using a Resource. The ConcreteUsage class implements the concrete resource usage algorithm. The surrounding code scopes provide the Strategy Context to the concrete usage algorithms.

  • Method Adapter. Doug Lea discusses the use of this pattern to implement before/after patterns in his book Concurrent Programming in Java. In this context, Java object monitors are typically the shared resources whose lifecycles are to be managed.

References

  1. Doug Lea, Concurrent Programming in Java, Addison-Wesley Publishing, Inc., 1996. ISBN 978-0201695816.