Business logic in models

Posted by jmls on 10-Jan-2011 04:38

As anyone who reads this forum would know, I've been on a bit of a journey with objects, from learning how to use them, to learning where to use them, let alone any best practice

So far, I am at the point where I have a very "loose" MVC type framework

1) My "model" is an object generated from the database with properties matching the table columns

2) My "view" is nothing more than a UI which displays the object

3) My "controller" is a set of methods in what I term a "library", with 1 such controller for each business logic entity.

I've been happy with this up to this point, as it seems to do what I want, allows for customisation and flexibility (I even have a web, cli, ABL, ABL .net and .net front end to certain functionality!!)

However, I've also been playing around with php and the yii framework (man, I will *never* complain about the 4gl again ) and their framework confuses me slightly.

In my framework:

Creating a new record:

1) create a new object from a model. "foo = new bar()"

2) assign the appropriate properties

3) call the "New" method in my library , passing the object as a parameter "barlib:New(foo)"

in yii,

1) create a new object from a model. "foo = new bar"

2) assign the appropriate properties

3) call the "save" method *in the model* "$model->Save()"

I was under an impression that a model should have no logic or code other than what was needed to instantiate the object. I can see a benefit of having "placeholder" methods like Save , as anything using the model does not need to know what library to use to update said model . How far do you take this, though ? Should all of the logic of the save method be moved into the model ? does not that increase memory usage for no reason ? (I may have 1000 objects of the same type open, each one with a 50-line method to save ? doesn't make sense to me - put the save method into a singleton library, pass object as parameter.)

IOW, I would think that the model save method be

PUBLIC METHOD VOID Save():

   barlib:instance:Save(THIS-OBJECT).

END METHOD.

then in the calling code, all you need is

3) foo:Save()

What are your thoughts ?

All Replies

Posted by rbf on 10-Jan-2011 08:05

These are very intesting questions. See my thoughts below.

1) My "model" is an object generated from the database with properties matching the table columns

That sounds oversimplified. No logical data model? And no logic as you discuss below.

2) My "view" is nothing more than a UI which displays the object

OK

3) My "controller" is a set of methods in what I term a "library", with 1 such controller for each business logic entity.

Not sure what you mean here. Are these methods in a control object per Model, per View, per Business Entity or are they all in a single "library"?

I've been happy with this up to this point, as it seems to do what I want, allows for customisation and flexibility (I even have a web, cli, ABL, ABL .net and .net front end to certain functionality!!)

Wow! Where do your Controlers and Models reside in case of a web interface?

Do you use an AppServer for the other interfaces?

What is cli?

However, I've also been playing around with php and the yii framework (man, I will *never* complain about the 4gl again ) and their framework confuses me slightly.

In my framework:

Creating a new record:

1) create a new object from a model. "foo = new bar()"

2) assign the appropriate properties

3) call the "New" method in my library , passing the object as a parameter "barlib:New(foo)"

in yii,

1) create a new object from a model. "foo = new bar"

2) assign the appropriate properties

3) call the "save" method *in the model* "$model->Save()"

I was under an impression that a model should have no logic or code other than what was needed to instantiate the object.

I don't understand the code samples, but my understanding is that your impression of a model is wrong. The model contains all the code that handles the data on the client and is user interface independent.

I was under an impression that a model should have no logic or code other than what was needed to instantiate the object. I can see a benefit of having "placeholder" methods like Save , as anything using the model does not need to know what library to use to update said model . How far do you take this, though ? Should all of the logic of the save method be moved into the model ? does not that increase memory usage for no reason ? (I may have 1000 objects of the same type open, each one with a 50-line method to save ? doesn't make sense to me - put the save method into a singleton library, pass object as parameter.)

IOW, I would think that the model save method be

PUBLIC METHOD VOID Save():

   barlib:instance:Save(THIS-OBJECT).

END METHOD.

then in the calling code, all you need is

3) foo:Save()

What are your thoughts ?

Well, indeed the save method and all other data related methods should be in the (generic) Model, how else could you override them (in your specific Model)?

How far do you take this, though ? Should all of the logic of the save method be moved into the model ? does not that increase memory usage for no reason ?

Yes it does, and I share your concerns. In the old days we could just put the standard methods in a standard library and publish events or invoke RUN NO-ERROR for code hooks. But this is AFAIK the only way to do it in the OO world. Not that I like it!

(I may have 1000 objects of the same type open, each one with a 50-line method to save ? doesn't make sense to me - put the save method into a singleton library, pass object as parameter.)

1000 objects? Wow! Are you starting Model instance for each record?

I wonder what best practices others come up with.

Posted by Peter Judge on 10-Jan-2011 08:22

PUBLIC METHOD VOID Save():

   barlib:instance:Save(THIS-OBJECT).

END METHOD.

then in the calling code, all you need is

3) foo:Save()

I favour this approach, since now you can change the implementation of the Save() method without changing the callers.

I was under an impression that a model should have no logic or code other

than what was needed to instantiate the object.

I think the separation of responsibilities is

i) View: Paints UI, collects events and forwards them to the controller/presenter. Cares about UI tech (.NET, Web, etc)

ii) Controller/presenter: oversees, manages 'screen' (or triad or whatever you want to call it). Starts model(s), view. Receives events from views, models, (other) presenters and interprets them into actions (I like to think of this as UI logic).  Makes decisions based on UI and Model events and performs other actions (in view, model, or both).

Technology agnostic (other than being ABL in our case).

iii) Model: knows how to get and save data, operate on it.

The presenter/controller just needs to know what the event to call on the component is, not what it does, really. So if someone presses the 'Save' button on a Gui for .NET screen:

btnSave_onClick():

    myPresenter:Save(new SaveParams()).   

    Catch Error:

       e:Cancel = true.

End.

IPresenter:Save(SaveParams):

   ValidateUIInput(SaveParams).

   myModel:Save(SaveParams).

   Catch Error:

      Undo, throw Error.

End.

IModel:Save(SaveParams)

    /* connects to service or db */

    /* does BUSINESS LOGIC validation */

    /* saves records */

    /* anything else? */

Note that the IModel:Save() can be a service adapter to a remote service or a local DB or a webservice or whatever. The important thing in this context is that the controller/presenter has no knowledge of where or how the Model saves away the data. It justs asks the Model to perform a save and leaves it at that.

>(I may have 1000 objects of the same type open, each one with a 50-line method to save ? doesn't make

sense to me - put the save method into a singleton library, pass object as

parameter.)

There's nothing wrong with each individual Model calling a ModelWorkerLibrary to do the work; you could also do this via inheritance if you chose. However, this does mean you need to follow the Model:Save() approach, otherwise the scope of the work you'll have to do gets big and messy (lots of source affected, lots of testing etc).

3) call the "New" method in my library , passing the object as a parameter "barlib:New(foo)"

If I may, calling the "Create" method "New" in an OOABL world is confusing: I had to read that a couple of times to figure out that you didn't mean "NEW Foo.Bar()".

-- peter

Posted by Peter Judge on 10-Jan-2011 08:23

Command Line Interface.

This is wicked cool since I think it'll make your testing significantly more automate-able.

Julian, do you have a persistent/shared/batchmode AVM that you talk to for this?

-- peter

Posted by jmls on 10-Jan-2011 08:32

The testing side can either be done from the cli or from a specific

unit test . I'm trying to follow some of yii's standards , so we are

starting to make use of "fixtures" (standard data) etc

As for the AVM side, I use curl and / or wget on the command line to

get / set the data required. This means I can reuse the webspeed

interface

Posted by Thomas Mercer-Hursh on 10-Jan-2011 11:57

I think this is a case where the specific questions are predicated on the current programming model and that rather than focusing first on the questions, we should step back and wonder about the programming model.  You clearly have done some good things here, but I think there are also some bits that may not be quite the way you may want them in the end.

For background to my view of this stuff I would suggest that you check out the layering modelss for UI that I talk about here http://www.cintegrity.com/content/OERA-20%0B-I-concept-now-what-do-I-do .   For general ideas to think about in OO best practice, I would check out http://www.cintegrity.com/content/Why-Are-People-Talking-About-Good-OO which is not just about Why, but also some quick run through ... at head hurting speed ... a bunch of important principles.

For starters, MVC is a UI pattern.  As shown in the diagrams referred to above, this may or may not mean that all of the components are on the client since RIA and ChUI clients, for example, have limited or no functionality on the physical client so one needs an additional server-side layer to support them ... which may or may not be the same machine as the BL and DA layers.  Whichever, the model is not actually going to ever "save" anything.  At most, it is going to send its data to a BL component which validates the data and builds an object of the type which will be persisted, does whatever it needs to do, and then decides that it is actually ready for persistence where things are passed back to the DA layer to actually be persisted.  So, whatever the Model does, it is closer to Done than Save.

The BL in the client code should be limited to validity checks on the UI, i.e., whatever object it builds is not a full-fledged business entity, but just the logic related to UI.

Posted by danielStafford on 11-Jan-2011 08:19

As anyone who reads this forum would know, I've been on a bit of a journey

I am on the same journey.  My work has evolved (I hope) as I rewrite the client-side code base of an application I wrote in Progress GUI. I can't tell you how many times I thought a version of this or that model / view / controller class is finally where I wanted it, and then spent two more weeks making "enhancements".

My "model" is an object generated from the database with properties matching the table columns

My current version of a model inherits from model.model (where common model methods hang out).  A model implements an interface with methods like saveChanges(cBufferName), DeleteRow(cBufferName), fetchData ( cWhereClause), InsertRow(cBufferName), etc.  I define protected temp-tables and datasets directly in the model class and also create / manage all dataset queries and buffers.  The saveChanges / fetchData methods still use the old logic (persistant procedures) that makes calls to the appserver where the business entity and data access layers reside.

 My "view" is nothing more than a UI which displays the object

Same for me.

My "controller" is a set of methods in what I term a "library", with 1 such controller for each business logic entity.

In the presenter class I create the model and view and subscribe to events in the view: bsCreateRow, bsCancelCreateRow, view_KeyDown, gridBeforeRowsInsert, etc.

Should all of the logic of the save method be moved into the model ?

In my case the model knows how to make saveChanges and fetchData requests (super:saveChanges(hDataset, "customer")) and the service interface knows how to manage appserver save and fetch requests.  All the business logic is on the application server.  If there is a problem ( a missing field value, a delete request denied, pre and post transaction procedure issues, etc.) an error is "thrown" up/down stream and an application error is thrown to the presenter.

Posted by jmls on 13-Jan-2011 11:41

I just want to check to see if it is me, or is this messy ?


Again, using the yii framework, one of the "tasks" to do is to assign a user to a project.  There is a project model, and a user model.

the code required is in the project model, and is this:

public function associateUserToProject($user)

{

  $sql = "INSERT INTO project_user_assignment (project_id,user_id) VALUES (:projectID, :userid)";

  $command = Yii:app()->db->createCommand($sql);

  $command->execute();

}

for some reason I can't quite fathom yet, I don't like this

1) It's in the model. Each model would have this code duplicated in memory

2) you have database access code in the model. Something I thought was anthema ?

3) the code for the project_user_assignment table is in the project model. Why isn't / doesn't it have it's own model and controller ? Or is it because it's a project "thing" ?

What I've done up until now is

1) define a model as purely properties

2) define a singleton library of business logic for each area of functionality

3) have the client execute the code. *this needs explaining

to take the above yii example and convert it to my "current" thinking I would

1) Get a userobject of the user required to add to the project

2) Get a projectobject of the project to add to

3) call the singleton library method :  lib.project:instance:associateUserToProject(userobject,projectobject)

now, from previous posts, I am moving towards having methods in the model to "hide" the implementation of the library calls, so my Project object would have an assignUser method, and the code would now be

projectobject:assignUser(userobject)

however, all this method does is

lib.project:instance:associateUserToProject(p_userobject,THIS-OBJECT)

this just seems a little "cleaner" than the yii approach. Or am I dreaming ?

Posted by Thomas Mercer-Hursh on 13-Jan-2011 14:28

I think your instincts are growing.  I agree with all your criticisms.

It is an important, but difficult lesson to realize that a great deal of what is done in 3GL OOs isn't actually Good OO.  It is all too often that theory is mouthed and not followed.

Posted by Peter Judge on 14-Jan-2011 08:11

public function associateUserToProject($user)

{

  $sql = "INSERT INTO project_user_assignment (project_id,user_id) VALUES

(:projectID, :userid)";

  $command = Yii:app()->db->createCommand($sql);

  $command->execute();

}

for some reason I can't quite fathom yet, I don't like this

It's not ABL?

1) It's in the model. Each model would have this code duplicated in memory

I'm not sure why this concerns you so.

2) you have database access code in the model. Something I thought was

anthema ?

You have to have DB access code somewhere, right? If all you have is MVP, then the Model is the right place. If you follow the OERA (or similar), then the Model will need to call the Business Components and thence to the DB, in it's allocated place.

3) the code for the project_user_assignment table is in the project model.

You'd have to ask the YII people that, but you could go either way, I'd think, depending on your philosophy on parent/child relationships in general (ie does the parent maintain the child relationship or the child the parent).

You'll also see circular relationships in some cases - like in some of the Ultra controls, where there are references to the parent on the child (UltraRow:Grid) and to the children on the parent (UltraGrid:Rows) (I've almost certainly got the class names wrong, but the principles are what I wanted to mention, so please don't jump down my throat ;)">I've almost certainly got the class names wrong, but the principles are what I wanted to mention, so please don't jump down my throat ;)). I think it depends on your data schema as well as your philosophy on these things in general.

Why isn't / doesn't it have it's own model and controller ? Or is it because it's a project "thing" ?

I see the Presenter/Controller being tightly-coupled (or more synchronous in nature) to the View, and loosely-coupled (or more asynchronous in nature) to the Presenter/Controller; so a Model doesn't really "have" a Presenter/Controller (as opposed to a View, which does).

Furthermore, there's no requirement for a 1:1 relationship between Presenter/Controller and Model: a Presenter/Controller can manage multiple Models contemporaneously, and knows the relationships between them. However, if there's one logical aspect to the data, then I'd have one Model based on a Business Entity (in OERA parlance), which is composed of multiple DB tables, and wherever you have your business logic would be where you'd maintain the user/project relationship.

What I've done up until now is

1) define a model as purely properties

My take on a Model is that it's more than just properties - there's most certainly behaviour associated with it. If you've got a design where the Model is on the UI client, and there's more business logic on an AppServer, I want to do some validation on the client first (is there a value for the Cust Name, for instance). I'll also do that validation on the server/business logic, since I can't always guarantee that the UI is performing the update, but doing things locally helps with performance for one thing.

There's also the question of who manipulates the Model - even in simple terms like querying the Model's data - when the Model just consists of properties. My take is that the Presenter is not that guy: the presenter simply asks the model for a row/record/data (ie Model:GetNext() );

now, from previous posts, I am moving towards having methods in the model to

"hide" the implementation of the library calls, so my Project object would

have an assignUser method, and the code would now be

projectobject:assignUser(userobject)

however, all this method does is

lib.project:instance:associateUserToProject(p_userobject,THIS-OBJECT)

This approach works nicely - and certainly if you're concerned about memory usage in the Model, then it's a good way to go. I believe that hiding this implementation behind an interface is more important than exactly how you chose to do this, and if you decide to change your design from a singleton/library-based approach to an inheritance-based approach, you can do so without breaking anything*.

-- peter

  • that's the theory, anyway, but we all know of the asynchronous nature of theory and practice

So apparently using square braces/brackets makes the PSDN site think you want a link (which is common for wiki-like syntax). Clicking on that link gives a weird result - obviously - so changed to 'normal' parens.
Message was edited by: Peter Judge

This thread is closed