Jump to content

How to tame Separation of Concerns overkill


Go to solution Solved by ignace,

Recommended Posts

I was doing various separation of concerns refactoring but I think I went too far.

 

I think I have too many layers and want to do a reality check.  Originally there was some code in the Product class that called native functionality directly.  Now it's just too many layers, I think.

 

So what I have now is:

"real" business-layer code (calls)

     --> (i.e.) Motor class (which calls)

     --> DAO layer (that calls)

     --> Data layer (that calls)

     --> a global function that calls native MySql function

 

Is there a healthier way to restructure this complexity without losing benefits of SOC and OO?

class Product
{
   ...
   {
       // business logic needs motors  
       $this->product->motor->loadMotors();
   }
}


//class to handle motor functions
class Motor
{   
    public function loadMotors()
    {
        return $this->motorDao->loadMotors();
    }
}


//layer to handle connection between "object" and "database result"
class LoadMotorDao
{
    public function loadMotors()
    {
        $motorDbResultObject = $this->motorData->giveMeMotors();
        
        //some data-to-object handling code

        return $motorObject;
    }
}

//class that purely returns just data from DB as-is
//typically a DB result set
class LoadMotorData
{
    public function giveMeMotors()
    {
        return db_query("SELECT ... ");
    }
}

//global function that calls native functionality
function db_query($query, $database = "", $conn = "")
{
    return ... mysql_db_query($database, $query, $conn);
}
Edited by dennis-fedco
  • Solution

Yes, you went too far. Also your business classes should not be aware of DB classes. If your Product needs a Motor then make sure your DAO injects it.

 

class ProductMapper {
  private $SQL_LOAD_PRODUCT_WITH_MOTOR = 'SELECT .. FROM .. JOIN .. WHERE ..';

  public function someBusinessIdentifiableMethod() {
    $rows = db_query($this->SQL_LOAD_PRODUCT_WITH_MOTOR, [..]);
    foreach ($rows as $row) {
      $products[] = $product = new Product();
      //..
      $product->setMotor($motor = new Motor());
    }
    return $products;
  }
}
Your Mapper class would query your database and then "map" the result to objects. Much like the ResultSetMapping from Doctrine.

Thanks

 

I understand that I went too far, but after that I am not sure I understand the points completely.

 

Product and Motor are my business classes.  The rest is various DB-functionality and computation and display.

 

Are you saying that my bussiness class depends on DB?  Where exactly is the dependency, because I don't think I am aware of it, or I don't see it.

 

The way Product and Motor interact are not as clear cut as I've made it appear to be and their purpose currently is more messy.  So I want to focus on overall theme and not on any specific usage of the clasess.  But, for example, I load motors and products separately and then run varoius simulations.  There is no direct motor-product connection.  More like there are "motors" and "product" and then they interact.

 

Can you also show an example of how to use the ProductMapper?

 

Is it just

   //using the mapper
   $products = (new ProductMapper())->someBusinessIdentifiableMethod(); //calls DB-functions directly

I do not see how it is different from this (what I have now) :

    //what I currently have - invoking DAO->Data->Db->MySQL 
    $motors = $this->product->motor->loadMotors(); //calls a DAO class, which calls DB functions direclty

They both provide me with the same stuff - product or motor objects that were loaded from DB in some way or another... my way takes a lot more calls (various object invocations) though.

Edited by dennis-fedco

I supose another question too is :

 

Why use a Mapper pattern opposed to any other DB pattern available?

 

And also I do plan to eventually (if time/resources allow) to migrate to Doctrine or similar layer

Edited by dennis-fedco

The difference is that in my case the Mapper has context so related objects are also loaded. While your chained method calls need to be called every time you need more data, resulting in more queries.

 

Also because your Entity uses the DAO directly it is actually tied to your DB which has the dependency the wrong way like an object created by a Factory object would be tied to its creator. These should be decoupled.

 

As to why the Mapper I link to this article:

http://stackoverflow.com/a/814479

thanks

 

I think I understand now and don't need any further help so far

 

but I am curious -- what is it that's undesirable towards dependency in the current version of code that I have?

namely, if I wish to change the underlying database, I do not really have to touch my object.  so there is some decomposition there that works.

 

I may not even have to change the MotorDAO or the MotorData class, and change just the db_query() function when I switch native functionality
If structure of SQL changes, then I have to change MotorData.  But other than that, my entitity remains intact.  Or is it?

 

What is bad about dependency in the code?

The dependency is "wrong" because your entities depend on a higher layer. In other words the DAO layer should be dependent upon the entities (Motor, Product) not the other way round.

 

Doctrine solves this problem by using Proxy Entity objects.

More on that here: http://stackoverflow.com/a/17787070

 

This is kinda the same as what you did with the exception that this dependency is hidden and inexistent when the full object is loaded. Which is also what the Mapper pattern allows you to do. For example when a User logs in, it may not be required to load the groups he is a member of. So in this case this becomes either null (resulting in an Exception or Fatal Error) or a ProxyCollection (in case of someone does access it, of course for performance reasons it is best to not be used).

 

However when you want to view the groups he is a member of it is required to load the Groups. Since you access business methods on the Mapper object. You know exactly what you should and should not load (or lazy-load).

Edited by ignace

I have been playing with this a lot recently.

And I thought -- Why do I have to populate Product with a Motor? 

 

I do not have to!  I guess if my business application needed a Product with a Motor, as a unit, then yes, that is one way to do it.
But if my application matches various combinations of Products and Motors, then I can have them be loaded separately via DataMappers. 

 

Basically whatever suits my needs.

This begs for a more general question -- If I say have an object that is comprised of smaller objects, when is it appropriate to load them all up and when is it appropriate to just load them on as-needed basis.

//do I end up with this
class BigProductMapper {
  public function loadEverythingUp()
  {
    //load all subproducts into BigProduct
  }
}


//or with this

class BigProductMapper {
  public function loadJustTheBigProduct() {}
}

class SubProductMapper1 {
  public function loadSubProduct1() {}
}

class SubProductMapper2 {
  public function loadSubProduct2() {}
}
...
class SubProductMapperN {
  public function loadSubProductN() {}
}

//have production code load things up as part of bigger class as-needed

Like I already said you know when to load up dependency X and Y when you call the method on the mapper. Loading extra during presentation will lead to problems. Doctrine provides this functionality just to not break your code. And it is also easy to trace back when reviewing your query log.

 

It'll show up something like:

 

SELECT .. FROM table WHERE id = 1
SELECT .. FROM table WHERE id = 2
SELECT .. FROM table WHERE id = 3
..
This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.