Welcome Guest, Not a member yet? Register   Sign In
How to do a business logic layer?
#1

(This post was last modified: 10-28-2015, 08:15 AM by PaulD. Edit Reason: Added an image )

So my intent was to introduce a business logic layer.

Previously
- Fat controller
     - Calls thin model
- View

Now I want:
- Thin controller
  - Calls business logic layer model
    - Calls database layer model
- View

Picture of what I mean attached.

So in my test I have a model folder with two subdirectories, database_layer and business_logic_layer, which contain models. I have it in my head that the models in the business_logic_layer will be the only entities calling the database_layer.

So the controller gets some info from the user, say, a form input.
The controller knows the user_id from the session, and knows that its job is to control the creation of, say, a new product. So the controller calls the model in the business_logic_layer concerned with adding products.

That model does things like, is the user allowed to add products, is product adding allowed etc. If so, call the database model which adds products and try to add the product. Perhaps then it calls the model that writes a history record and so on. It then returns to the controller success or failure or whatever.

The controller then controls which view to show next (either the 'that didn't work' view or the 'you are not allowed view' or the 'what next' view etc.)

Is this the right way to do this? So if my database structure is changed, I can adjust only the database model layer and all will still work. If my business logic changes, I can just adjust the business logic layer. If my website changes, I can adjust the controller?

So a model calls another model? This is the bit I am not unsure about. Is that good practice?

So my business_logic layer model might look something like:
PHP Code:
public class Product_manager extends CI_Model{
 
   function create_product(...){

 
       // check permissions
 
       $this->load->model('database_layer/permissions_table');
 
       $this->permissions_table->check_allowed(...);
 
       
        
...

 
       // add product
 
       $this->load->model('database_layer/product_table');
 
       $this->product_table->add_product(...);
 
       
        
...

 
   }


Is that the right way to implement a business logic layer?

Thanks in advance for your advice, this is the first time I have attempted a business_logic layer and super thin controllers.

Paul.

Attached Files Thumbnail(s)
   
Reply
#2

There shouldn't be any problems calling a model from another model. As long as the separation between layers is maintained, so the only model the controller uses is the model from the business logic layer, and models in the business logic layer are only accessing the database through models in the database layer, it should be fine.

An argument could be made to use libraries in either layer, but I'm only a fan of this approach when the database layer is a single library used by all of the models in the system, taking the place of CI's Database library or acting as a layer between the models and CI's Database library. If I want to access data from my controller, I expect to load a model. I don't really care if the data is retrieved from a database table, a RESTful service, the file system, or a collection of other models. The model should deal with that, enlisting whatever library (or model) it needs to handle the data.
Reply
#3

Thank you.

It was all working but I have heard (or read) people saying things like 'a model should not call a model' or similar.

I thought about using libraries as well but they didn't seem appropriate as there is no stand alone or contained logic to them. i.e I could not use the library anywhere else, which is how, rightly or wrongly, I regard libraries.

Thank you for the response. I am really looking forward to working with this approach! :-)

Paul.
Reply
#4

The tact I've been using on my last couple of projects is similar to what you've described in concept, but a bit different in execution.

I'm using the Repository Pattern so the database layer knows how to do the basic CRUD stuff. I also keep any methods in there with nice, descriptive names so that the calling code is nice. When it returns results, it returns them as a custom result object, which is simply a class that represents the entity. I'll explain it all better in just a second.

To keep things nice and organized, I'm using Composer to make it simple to load the Entity classes whenever they're needed. They are simple classes, with class properties that match up to the database field names, since CI's db layer will need them to match exactly when it creates the return object. These Entity objects do not know anything about the database or where the data comes from. They are purely business logic. So one might look something like:

Code:
class Artist {

    // Match database column names
    protected $id;
    protected $title;
    protected $short_desc;
    protected $description;
    protected $image;
    protected $header_image;

    // Misc functions here...
}

The functions it includes would be getters and setters that ensure logic is met.

While CI makes it simple to retrieve objects like this, they don't make it simple to save it, unless the object is a very simple object. I don't recall the limitations I hit with it previously but it's simple to get around with custom update and create methods that simply read the properties from the object and save it to the db.

As for application structure, I've started keeping a lot of my app-specific code (these two layers specifically) separate from the app, and load it all up with Composer. One of the main reasons is to make it simpler to port to different frameworks, if need be, over the lifetime of the application. That could be a port to CI 4 when it's out, or another dev switching it over to Laravel, etc. I'm becoming very fond of the idea of keeping the application as self-contained as possible and using the framework for functionality, instead of building an application on top of the framework.

So far, this seems to be working fairly well, though I do think I'll have to include a Dependency Injection Container next time to keep the number of instances of objects in memory simpler to manage.

Hope that makes sense. Feel like I rambled a bit.
Reply
#5

(This post was last modified: 10-30-2015, 02:36 AM by pdthinh.)

I'm interesting in this threads and I have a question. Assume I have a Customer table and an Order table with a one-to-many relationship . I want to get all (or many) customers with their corresponding orders in the following way:

PHP Code:
$orders = [];
foreach (
$customers as $customer)
{
   $orders[] = $customer->getOrders();


While this code is straightforward I think it run too much queries to the database. Is there a better solution to do this?
Reply
#6

(This post was last modified: 10-30-2015, 05:03 AM by PaulD. Edit Reason: small clarification )

@kilishan

Wow! I never knew about custom result objects. They look fascinating AND just perfect! I am certainly going to be having an experiment with those. Thank you.

The Repository pattern does seem a step too far for me, at least for now, but those links were an interesting read. But having a class to represent an entity is such a great idea! It just makes so much sense.

Thank you again,

Paul.
Reply
#7

(This post was last modified: 10-30-2015, 07:09 AM by kilishan.)

(10-30-2015, 05:02 AM)PaulD Wrote: @kilishan

Wow! I never knew about custom result objects. They look fascinating AND just perfect! I am certainly going to be having an experiment with those. Thank you.

I just discovered it a few months ago, actually. I was so thrilled I had to update the docs to include info about it. Smile I guess it's been there since v2, but was barely mentioned.

(10-30-2015, 05:02 AM)PaulD Wrote: The Repository pattern does seem a step too far for me, at least for now, but those links were an interesting read. But having a class to represent an entity is such a great idea! It just makes so much sense.

Thank you again,

Paul.

That article makes it sound a bit more complex than it needs to be, I think. For my uses, the Model is the "brokering layer" it talks about. I cheat a little because it does know what methods to call to get the data it needs, so it is at least slightly aware of the Entity class, but the Entity class is not aware of the model or the database at all. It will need to use other models for relationships, of course.
Reply
#8

(10-30-2015, 02:35 AM)pdthinh Wrote: I'm interesting in this threads and I have a question. Assume I have a Customer table and an Order table with a one-to-many relationship . I want to get all (or many) customers with their corresponding orders in the following way:




PHP Code:
$orders = [];
foreach (
$customers as $customer)
{
   $orders[] = $customer->getOrders();


While this code is straightforward I think it run too much queries to the database. Is there a better solution to do this?

That's when you have to get a little more creative, and it's not quite as straight-forward. You would need to collect all of your customer id's, then have a new method that took that array of customer ids and grabbed all orders using where_in. Then you'd likely have to loop over the results to get it organized into groups by customer. Something like: 

Code:
$customer_ids = array_column($customers, 'id');

$query = $this->db->where_in('customer_id', $customer_ids)->get('orders');

$orders = $query->result_array();

$ordered_orders = [];

foreach ($orders as $order)
{
    $ordered_orders[$order['customer_id']] = $order;
}
This would leave $ordered_orders have keys that were customer ids so you could quickly get to their orders.
Reply
#9

(10-30-2015, 07:06 AM)kilishan Wrote:
(10-30-2015, 02:35 AM)pdthinh Wrote: I'm interesting in this threads and I have a question. Assume I have a Customer table and an Order table with a one-to-many relationship . I want to get all (or many) customers with their corresponding orders in the following way:





PHP Code:
$orders = [];
foreach (
$customers as $customer)
{
   $orders[] = $customer->getOrders();


While this code is straightforward I think it run too much queries to the database. Is there a better solution to do this?

That's when you have to get a little more creative, and it's not quite as straight-forward. You would need to collect all of your customer id's, then have a new method that took that array of customer ids and grabbed all orders using where_in. Then you'd likely have to loop over the results to get it organized into groups by customer. Something like: 


Code:
$customer_ids = array_column($customers, 'id');

$query = $this->db->where_in('customer_id', $customer_ids)->get('orders');

$orders = $query->result_array();

$ordered_orders = [];

foreach ($orders as $order)
{
$ordered_orders[$order['customer_id']] = $order;
}
This would leave $ordered_orders have keys that were customer ids so you could quickly get to their orders.

Thanks for your reply. I used to rely on PDO prepared statements to handle repeated queries. I think your solution is better and I will try it.
Reply




Theme © iAndrew 2016 - Forum software by © MyBB