vendredi 5 mai 2017

Synchronous thread safe APIs for exposing cached data

We offer a package which interfaces with a bunch of other packages who's APIs are not thread safe. Our package's API is entirely message based and therefore asynchronous to allow thread safety for users of our package. Our package therefore wraps a bunch of non-thread safe packages and offers a thread-safe API. That means that users of our package can interface with our package from any thread.

We would like to add synchronous APIs to our package while maintaining thread safety. I've done some research and have come up with two possible patterns to do this which I will share below. I'm not entirely happy with either approach so am wondering if the community may have more suggestions for patterns we can use. Note that the code below is for design and illustration purposes (c++ pseudocode) so is not meant to compile.

Approach 1 - Package users dependency inject data access classes to our package. We access these classes using run time type inference.

// Define an interface class for all data accessor classes
class DataAccessor
{

}

// Some random data
class FooData
{
    int foo;
    int bar;
}

// A concrete data accessor
class FooDataAccessor : public DataAccessor
{
public:
    FooData getFooData();
    void setFooData(FooData& fooData);

private:
    FooData cachedFooData;
    CriticalSection dataCriticalSection; //Use this for locking cachedFooData to set the data.
}

class OurPackage
{
    OurPackage(std::vector<DataAccessor*>); //constructor which is injected the data accessors so that our package customers can own their lifecycle.
}

// How package users would inject the data accessors into our package.
int main()
{
    std::vector<DataAccessor*> dataAccessors;
    //Populate the dataAccessors with the customers needs
    auto package = new OurPackage(dataAccessors);
}

// In OurPackage, set the actual data in caches
for (DataAccessor* dataAccessor : dataAccessors)
{
    //Use RTTI to find the instance we want
    if (auto dataAccessorTypeWeWant = dynamic_cast<DataAccessorTypeWeWant*>(dataAccessor) != nullptr)
    {
        //Set the data on dataAccessorTypeWeWant 
        break;
    }
}

2 - Use a singleton pattern instead

If the data access caches are singletons instead, package users don't have to manage the lifecycle of these classes and don't need to worry about passing pointers to instances of the data access caches around their application. This has all the pitfalls of singletons though.

Aucun commentaire:

Enregistrer un commentaire