Jump to content


Photo

Where to locate transactions


  • Please log in to reply
5 replies to this topic

#1 NotionCommotion

NotionCommotion
  • Members
  • PipPipPip
  • Advanced Member
  • 1,601 posts

Posted 11 May 2018 - 12:16 AM

Previously, I've used them in the model/mapper (still a little fuzzy on the difference), but doing so reduce my ability to reuse code, so now I think it should be higher up at the service/controller.  But this level doesn't know if the queries will be atomic, so it seems every call to the mapper must be wrapped with beginTransaction/rollBack/commit.

 

Where should they be located?

 

If at a higher level, what is the best way to minimize code?  Was thing of something like the following.  I don't think doing it above the service is appropriate as it is too limiting.

 

Thanks

 

class SomeService{
    public function someAction(){
        $this->doTransation(function(){$this->callModel(123);});
    }

    protected function doTransation($f){
        echo("beginTransaction\n");
        try {
            $f();
            echo("commit\n");
        } catch (\PDOException $e) {
            echo("rollBack\n");
            throw($e);
        }
    }

    protected function callModel($f){
        echo("call model\n");
    }
}
$service=new SomeService();
$service->someAction();

Edited by NotionCommotion, 11 May 2018 - 12:20 AM.

NotionCommotion

#2 requinix

requinix
  • Administrators
  • Impoverished Administrator
  • 9,874 posts
  • LocationWA

Posted 12 May 2018 - 12:51 AM

Somewhere in the call stack between the user and the database will (should) be a point where the code knows that it is dealing with the database and that it will be executing multiple queries. It's the place where all the required pieces of work are coordinated to work together to get the end result. If you don't have such a place then you should refactor a bit so that there is one.
That's where the transactions are set up.

$f() is apparently where the main work would be. That's probably where the transaction should happen. It could delegate work off to other places, but still $f would be the one that knows what's happening. Not in the service because, while it may know the database is involved, it doesn't know there are multiple queries - it only has the one function call.
"Basically, I think the general rule of thumb is: if someone really wants the blood that's inside of your body, and they're like a vampire, or a dracula, or some sort of man-squito, then that's probably okay. A dracula and a man-squito are made for removing things like blood and swords from inside your body. That's basically fine. If something wants to get at your blood and they're, say, some kind of murdersaurus, or maybe a really big frog, that's where the problems start to arise. A really big frog is not made for removing blood, and your blood knows this, which is why it is so vehement about wanting to stay in your body instead of coming out. Unfortunately this will not deter a really big frog because a really big frog is full of things like prizes, and value, and quite a lot of hatred, and it would really rather like to replace any and all of those things with your blood, and basically by any means possible." --slumbermancer

#3 NotionCommotion

NotionCommotion
  • Members
  • PipPipPip
  • Advanced Member
  • 1,601 posts

Posted 12 May 2018 - 02:19 AM

Thanks requinix,
 
Assuming a service is only called once from an action (maybe a big assumption), then someAction() is where the work is really performed, and I could have just done:
 
    public function someAction(){
        echo("beginTransaction\n");
        try {
            echo("call model\n");
            echo("commit\n");
        } catch (\PDOException $e) {
            echo("rollBack\n");
            throw($e);
        }
    }
As I expect you know, $f was just my cute attempt to reduce script.
 
If multiple services are called, then I expect I should do this:
$app->post('bla/bla/ble', function (Request $request, Response $response) {
    echo("beginTransaction\n");
    try {
        $rs=$this->get('SomeService')->someAction($request->getParsedBody());
        $rs=$this->get('SomeOtherService')->someAction($request->getParsedBody());
        echo("commit\n");
    } catch (\PDOException $e) {
        echo("rollBack\n");
        throw($e);
    }
});

 

But for both these cases, assuming I didn't write the model or if I did I forgot what I wrote, I need to arbitrarily use transactions whether the are really needed or not.

 

No?


NotionCommotion

#4 requinix

requinix
  • Administrators
  • Impoverished Administrator
  • 9,874 posts
  • LocationWA

Posted 12 May 2018 - 04:15 AM

I think it's too abstract for its own good. Not just your question but the architecture.

You have these two service things that each do their own thing that you don't really know much about. Do they work with the database? Probably. But what do they do? Will they stop after one query? Does one make database changes that the other implicitly depends on? Must one always execute before the other? If the first one succeeds and the second fails, does the first need to know about it and potentially take action beyond simply reverting its changes? Or are they unrelated and it's just that they both need whatever they do at the same time, and must both be successful for it to matter?

In your shoes I'd be tempted to rewrite both into one service. Or make a new service that copies from both. Whatever so long as there is a single point of responsibility for the sum of actions that are required. Which brings me back to that being the place where transactions should be.
Of course it could be that's what you have there... It feels more like a controller and the two things are models. I don't know.

But as a quick solution, yes: that code would be where the transaction is. It doesn't know for sure that the database is involved, or that there will be multiple queries (beyond presumably one for each dependent service), but at least it is where the work is coordinated.
"Basically, I think the general rule of thumb is: if someone really wants the blood that's inside of your body, and they're like a vampire, or a dracula, or some sort of man-squito, then that's probably okay. A dracula and a man-squito are made for removing things like blood and swords from inside your body. That's basically fine. If something wants to get at your blood and they're, say, some kind of murdersaurus, or maybe a really big frog, that's where the problems start to arise. A really big frog is not made for removing blood, and your blood knows this, which is why it is so vehement about wanting to stay in your body instead of coming out. Unfortunately this will not deter a really big frog because a really big frog is full of things like prizes, and value, and quite a lot of hatred, and it would really rather like to replace any and all of those things with your blood, and basically by any means possible." --slumbermancer

#5 NotionCommotion

NotionCommotion
  • Members
  • PipPipPip
  • Advanced Member
  • 1,601 posts

Posted 12 May 2018 - 02:31 PM

Thanks requinix,
 
I agree that the question and especially the architecture is too abstract.
 
Your remarks about the two service things were right on.  Most of the time, a decent ux can have a single clear point between the call stack and database.  For instance, one action to first create a bar chart which does not have any series or categories, and later a second action to add a series or category to the chart.  But to create a gauge chart, the user will want to create both the chart plus add a single series to it as a single action.  I think I will attempt to limit these occurrences and handle them on a case by case basis.
 

Of course it could be that's what you have there... It feels more like a controller and the two things are models. I don't know.

 

Please elaborate on this.
 
Thanks

NotionCommotion

#6 requinix

requinix
  • Administrators
  • Impoverished Administrator
  • 9,874 posts
  • LocationWA

Posted 12 May 2018 - 06:34 PM

Nevermind. I was thinking about the original code where you had a class named SomeService and combining it with the later code that showed the SomeService and SomeOtherService, and the result of that was you having one service that calls other services. But you don't actually have that.
"Basically, I think the general rule of thumb is: if someone really wants the blood that's inside of your body, and they're like a vampire, or a dracula, or some sort of man-squito, then that's probably okay. A dracula and a man-squito are made for removing things like blood and swords from inside your body. That's basically fine. If something wants to get at your blood and they're, say, some kind of murdersaurus, or maybe a really big frog, that's where the problems start to arise. A really big frog is not made for removing blood, and your blood knows this, which is why it is so vehement about wanting to stay in your body instead of coming out. Unfortunately this will not deter a really big frog because a really big frog is full of things like prizes, and value, and quite a lot of hatred, and it would really rather like to replace any and all of those things with your blood, and basically by any means possible." --slumbermancer




0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users