2

While learning from @tereško's answer on how should a model be structured, I can't find what is the best way for communication between the services. I also found this answer which is a similar situation I'm dealing with, but the example is great for showcase. So, in that sense, how do I retrieve the recognition service inside of a Blog service for example, when the service has only access to a domain and mapper factory objects. Do I send it from the controller? Or do I need to send a service factory too?

Update: I did not provide an example since I mentioned that the second answer of tereško is pretty similar to what I am trying to achieve. I am trying to build a BlogService that for example stores a post. In order to store the author of the post, I am trying to retrieve the (his called) recognition service in order to get the logged in user (which is apparently the author of the post).

class BlogService
{
    public function storePost($title, $content)
    {
        $post   = $this->domainFactory->build('Post');
        $mapper = $this->mapperFactory->build('Post');

        $post->setTitle($title);
        $post->setContent($content);
        $post->setAuthor( /* get logged in user */ );

        $mapper->save($post);
    }
}
Community
  • 1
  • 1
Darko
  • 221
  • 1
  • 14
  • 2
    Can you provide an example of what you are trying to do? – pquest Dec 15 '14 at 21:19
  • There's a difference between a model and a service. And the answer is going to depend on your actual use case and be opinion based. You'd probably want to pull in data from a service at the same layer you would retrieve data from a database or any other external resource. – Anthony Dec 15 '14 at 21:22
  • I have updated my question with an example. @Anthony Are you saying that I need to retrieve the service inside of a mapper or repository? Didn't quite understand you. – Darko Dec 15 '14 at 21:31

2 Answers2

11

The theory

So basically your question is "how to share a domain object from Recognition service with some Content service?". I have been actually thinking on this for some time.

As I see it, there are 4 options here. Two shitty, one good one and one in-between:

  1. Previously mentioned "pass the service factory" approach.

    This is the most naive solution, because in a real world situation you will end up with uncontrollable growth for the object graph - service containing services containing services .. ad nauseum.

    It's unsustainable, but quick.

  2. Alter the Recognition service in such a way, that it spits back an Account instance to the controller, which then in turn passes is to other services.

    Essentially you would end up creating an intentional leak between layers in order to minimize complexity. You could call this "architectural denormalization".

    It's a hack and a bad architecture.

  3. Use a DI container to share domain objects (and even mappers) between services.

    This way is more elaborate previous two, but it also would let you get rid of the factories inside your services entirely.

    Best option, but requires additional skill and code.

  4. Use your domain object factory to create the instance of Account (and either all others or selected ones) only once.

    Kinda like in this post, but for domain objects. Of course you will need some way to also bypass the "cache", because a large group of domain objects will require an ability to be initiated more than once. Thus - a need for some configuration.

    A tricky half-way solution, but lets you keep familiar API within services.

Confession

I myself am currently using the option 2. The reason for it is that I don't have a DI container which I could use (and I thought of "option 4" only 5 minutes ago). Most of what you see under description "PHP DI container" are actually various service locators (in which case you are better off with bunch of factories anyway).

If you want to go with DI container, my endorsement goes to Auryn.

And reason I am not using it myself is, because I am really arrogant, I want to make my own DI container.

P.S.

Instead of writing this:

 $post->setAuthor($user);

You should be writing something like this:

$post->setAuthorId($user->getId());

.. for two reasons:

  • You are not using an active record. Therefore you have no need for the Post instance to manage it's relations with other entities. That's what you have a service for.
  • If you passed the entire Account instance, you would end up extracting the ID anyway. So you would be violating Law of Demeter of no practical benefit.
Community
  • 1
  • 1
tereško
  • 56,151
  • 24
  • 92
  • 147
  • Hurray for arrogance! – Anthony Dec 16 '14 at 00:44
  • I even started it, but "the work" happened and it ended up rotting in the `/workspace/lab` folder. – tereško Dec 16 '14 at 00:47
  • I've been studying the Auryn injector today (I've never heard of it before), and it seems to be an excellent piece of work, which led to me to a conclusion to go with the third option. I just have this doubt now, in my retrievePost method I have a Post domain object, and a Post mapper. Then in my retrievePosts method I have a PostCollection domain object, and a PostCollection mapper. And then there's the recognition service that we mentioned. Does this mean that I need to send 5 dependencies to the Blog service? Or should I split the service? – Darko Dec 16 '14 at 15:28
  • If you don't need them in every method in the class, just put the dependencies on the methods and don't store them in the class properties. In fact you could probably stop having BlogService be a class at all, and just have a collection of functions that you point your router/dispatcher to. – Danack Dec 16 '14 at 16:13
  • @Danack The first suggestion seems nice, but isn't the second one going to "leak" the business logic in a wrong layer? – Darko Dec 16 '14 at 17:39
  • @tereško I think I'll split them into smaller ones, the decorator seems an overkill to me at this moment, but I'll have it in mind for later times. – Darko Dec 16 '14 at 17:40
  • @Darko , there is actually a third option. Only inject the shared dependencies. – tereško Dec 16 '14 at 17:53
  • But what about the others in this case? How do I pass them? – Darko Dec 16 '14 at 18:04
  • "isn't the second one going to "leak" the business logic in a wrong layer?" No...it's no different from routing information. – Danack Dec 16 '14 at 19:44
  • @Danack that approach seems to be sustainable only in really small applications/ – tereško Dec 17 '14 at 02:23
  • To avoid "leak between layers" in Option No.2, don't send Account object back to Controller. Instead create AccountDTO, which has data only, and use it to send from Service to Controller and then from Controller to another Service. (same procedure with the View). Cause if you think about it, you don't need logic to send between Services, but data only. New object, made for 'transportation' is separate from Domain Object. So no leak. – Andrew Jan 21 '15 at 18:40
1

My guess is that domainFactory and mapperFactory are instances of Abstract Factory pattern.

Since your BlogService already requires those 2 abstract factories why not have the third, ServiceFactory.

Responsibility of this object would be to create (preferably abstract) products, Services.

http://en.wikipedia.org/wiki/Abstract_factory_pattern

Weltschmerz
  • 2,076
  • 1
  • 14
  • 17
  • Are you suggesting on having one factory class that will be sent to services and be responsible for creating services, mappers and domain objects, or having three factories sent to the services? – Darko Dec 15 '14 at 21:53
  • What I'm saying there is to have BlogService use three factories. There's nothing preventing you from having factory of factories if that makes sense in your app. You can also consider having BlogService require the logged in User itself. – Weltschmerz Dec 15 '14 at 22:03