vendredi 31 juillet 2015

What is the correct place to put checking of conditions in this use case?

Here is the situation:

I have a database and a class that is basically a representation of a set of fields of different tables in it. Let's call it Message. "Message" does not know of the database and whenever I need to synchronize it to DB I use separate functions accepting instances of Message to do it.

Whenever I need to do some operation that involves synchronizing data from "Message" to database, I do not just call the Modifier1/Modifier2 functions because more often than not, I need to either pass or roll back the whole sequence. (usually when ORA- DB error occurs)

So, I have a queue of actions set up:

std::list<std::function<DBType>>

In reality there is a wrapper class around this queue that receives actions via signals/slots mechanism and executes the whole accumulated pack of actions on:

Execute();

So, it's like this : if I need to update the database in some piece of code:

   // sending update #1
   emit dbAction(std::bind<CREATE SOME FUNCTOR HERE>);
   // sending update #2
   emit dbAction(std::bind<CREATE SOME OTHER FUNCTOR HERE>;
   // flushing the queue
   emit flushDb();

the actions execute, everyone is happy. End of the story. Until now that is. Now, I realized I have to have a set of preconditions on "Message" that will prevent these actions from passing through.

Options available:

1) To inject verification code into called functions. But the conditions have nothing to do with them and entirely depend on external context.

2) To let user of the queue to check conditions in outside code. But let's be realistic - even if I am the said user, what's not enforced, is easily forgotten.

3) To use some class to emit these actions after checking preconditions on the arguments. But this will require to update its interface for any new action and will not allow to just send a lambda through.

4) My current insane solution: instead of just sending std::function with already bound arguments, create a templated functor that keeps parameters accessible until they are actually needed by the queue.

//abstract class so that I can store unrelated template instantiations in a single container in my queue
class  IMessageActionDB
{
  public:
    virtual ~IMessageActionDB();
    enum EActionTypes
    {
          noaction = 0,
      action_type_1 = 1,
      //...
      action_type_N = N
    };

    void operator()(DBType db)
    {
        if(!Verify())
            throw exception;
        BindToExecutable();
        calledFunc(db);
    };
     // will bind whatever is stored in MessageActionDB to the calledFunc
    virtual void BindToExecutable() = 0;
    // will check the action with supplied checker functor
    bool Verify() = 0;

    protected:
    std::function<void(DBType)> calledFunc;
};

// templated action that will accept any function/number of parameters for later use
template<class FunctionType, class ...Ts>
 class  MessageActionDB : public IMessageActionDB
{
  public:
    MessageActionDB(FunctionType f, std::tuple<Ts...> t);

    void BindToExecutable();
    {
        //perform template mumbo jumbo to bind func and parameterPack to calledFunc
        calledFunc = bind_tuple(func, parameterPack)
    }
    bool Verify() 
    {
        //call verifiers in order returning if action satisfies or not
        return result;
    };

  private:
     std::tuple<Ts...> parameterPack;
     FunctionType func;
     std::list<std::function<bool(Ts...)>> actionVerifier;
}; 

This way, I can assign verifier for action based on its EActionTypes type, and queue wrapper can use Verify function at invokation time to check if the action is allowed.

Is this sane? Is there a better way/place to inject checking of preconditions?

Quite frankly, my solution feels awkward, I can't shake the feeling that I've done something completely wrong.

Aucun commentaire:

Enregistrer un commentaire