Record level Entity Scopes

Discuss all feature requests you have for a new Servoy versions here. Make sure to be clear about what you want, provide an example and indicate how important the feature is for you

Record level Entity Scopes

Postby jgarfield » Fri Sep 28, 2012 8:31 pm

Since Entity Scopes are one of the things that I'm incredibly excited about in Servoy 6 it's been on my mind quite a bit. We already have big plans to do some very thorough code reorganization and refactoring to fit into this beautiful new model, but in thinking about it I've come up with a bit of an issue.

Frequently I'm going to want to be performing some action on a specific record in a foundset, but not necessarily the selected record.

The obvious simple example is a column setter where when the value of a column changes, you also need to perform other actions. Of course you *could* put something like that on the foundset's Entity Scope, but now your code is going to have to look something like
Code: Select all
rMyRecord.foundset.setMyCol(rMyRecord, value)

which, I don't think it would be too controversial to say is a little clumsy, a lot ugly, and really doesn't even feel like the right place to be putting code like that.

Obviously a setter isn't the only scenario where this would be useful. Anytime you are looking to perform some action with respect to an individual record it seems like a Record level Entity Scope would be an appropriate space for it.

Thoughts? :D
Programmer.
adBlocks
http://www.adblocks.com
jgarfield
 
Posts: 223
Joined: Wed Sep 28, 2005 9:02 pm
Location: Boston, US

Re: Record level Entity Scopes

Postby david » Mon Oct 01, 2012 7:15 pm

Seems to me that records that are so important as to benefit from their own entity scope shouldn't be in that table at that point.
David Workman, Kabootit

Image
Everything you need to build great apps with Servoy
User avatar
david
 
Posts: 1723
Joined: Thu Apr 24, 2003 4:18 pm
Location: Washington, D.C.

Re: Record level Entity Scopes

Postby jgarfield » Mon Oct 01, 2012 8:52 pm

david wrote:Seems to me that records that are so important as to benefit from their own entity scope shouldn't be in that table at that point.


I'm not 100% certain I understand what you mean here, but to be clear I'm not advocating for an individual/specific record (e.g. item record with id 4) having it's own entity scope, I'm advocating for scopes at a record level (i.e. a lower level than foundset).

For instance, let's say there's a "tasks" table. There could be a foundset entity scope method "loadTasksCreatedToday", that would perform a find on that foundset for tasks that were created today.

Once I've found that set of tasks, I may want to set a number of them to a completed status. Let's pretend for a minute that marking a task as competed requires a little more work than just setting iscompete = 1.

Our current options are:
Old: Create a form method somewhere that contains the code you need to run every time you set iscomplete = 1
Code: Select all
function setIsComplete(rTask, bComplete) {
   if (bComplete) {
      rTask.iscomlete = 1;
      rTask.f_task__user.lastactiondate = new Date(); //just for instance
   }
   else {  //other stuff }
}


but that is old and gross as far as ways to do things.

New: Create a method in the entity scope. Unless I'm mistaken this either requires the record you want to be the SELECTED record, or for you to pass in a record/record index that you want to work with.

Code: Select all
foundset.loadTasksCreatedToday();
foundset.setSelectedIndex(3);   //Inefficient
foundset.setIsComplete(true);
//OR
var rTask = foundset.getRecord(3);
foundset.setIsComplete(rTask, true); //eh
//OR pretend we've passed the record around
rTask.foundset.setIsComplete(rTask, true); //also gross



Proposed: Record level scope where the code executes with reference to a specific record, not a specific foundset

Code: Select all
foundset.loadTasksCreatedToday();
var rTask = foundset.getRecord(3);
rTask.setIsComplete(true); //hooray!
Programmer.
adBlocks
http://www.adblocks.com
jgarfield
 
Posts: 223
Joined: Wed Sep 28, 2005 9:02 pm
Location: Boston, US

Re: Record level Entity Scopes

Postby david » Mon Oct 01, 2012 9:53 pm

Gottit now. I like it if you're going down that route to organize code.

I think though that there is a better way to organize code than via entity scopes. The only reason we would create an entity scope method is to assign something to a table event.

Instead we create API's for each of our modules and pass params (foundsets, records, whatever) to the API methods. To use your code, the result would be:

Code: Select all
foundset.loadTasksCreatedToday();
var rTask = foundset.getRecord(3);
globals.someAPI.recordFunctionsNode.setIsComplete(rTask)


Several things we like about this approach:

1- coding style is more readable as to what is happening
2- api is available to other modules
3- api is available to web services
4- core code is organized in one spot
5- code completion
6- inline code documentation

Example of how we set up an API:

Code: Select all
/**
* API for QuickBooks
* [utils]      Utility node
* [callCache]   Calls to local cache
* [callQB]      QuickBook calls
*
* @properties={typeid:35,uuid:"6298D49D-E3BB-471F-9705-C6C50AAD8F58",variableType:-4}
*/
var QB = new function() {
   /**
    * @type {Object}
    *
    * Connection information used to make calls to Intuit online
    */
   var company =  {
      id : '',
      name : '',
      realm : '',
      datasource : ''
   }
   
   /**
    * @type {Boolean}
    *
    * Flag to hide notifications
    */
   var showNotifications = false
   
   /**
    * Utility node
    */
   this.utils = new function() {
      /**
       * Make sure that a connection is available to Intuit
       *
       * @param {JSRecord<db:/rsw_solutions/qb_company>} record Specify info for connecting to quickbooks
       * @return {Boolean} Valid connection to QuickBooks possible
       */
      this.initialize = function(record) {
         //parameters specified for quickbooks connection
         if (record) {
            company.id = record.id_company
            company.name = record.company_name
etc....


What it looks like when used in a method editor (code completion and inline docs):
 
Screen shot 2012-10-01 at 3.41.35 PM.png
Screen shot 2012-10-01 at 3.41.35 PM.png (107.58 KiB) Viewed 1841 times
David Workman, Kabootit

Image
Everything you need to build great apps with Servoy
User avatar
david
 
Posts: 1723
Joined: Thu Apr 24, 2003 4:18 pm
Location: Washington, D.C.

Re: Record level Entity Scopes

Postby jgarfield » Mon Oct 01, 2012 11:57 pm

david wrote:Gottit now. I like it if you're going down that route to organize code.


Yes, this is definitely about code organization, but it's certainly not just about that.

Hypothetically what's going on when you define an Entity Scope (in OOP style terms) is you are Extending the generic Foundset class Servoy provides for your table. This is fantastic because it provides a correct place to define the Model/Controller portions of your MVC application, but only with regards to a Collection of a particular entity.

When looking at it in that light it seems like you're taking a step backwards to now have to define Model/Controller type behaviors for your Individual Entities in some sort of global scope. (As an aside, why wouldn't your make you API code in specific scopes, instead of globals?)

In response to your points
david wrote:1- coding style is more readable as to what is happening

Code: Select all
rTark.setIsComplete(true)
vs.
Code: Select all
globals.someAPI.recordFunctionsNode.setIsComplete(rTask)

We're going to have agree to disagree on readability here.

david wrote:2- api is available to other modules

I see no reason why Record Scopes wouldn't be available to other modules like foundset scopes are.

david wrote:3- api is available to web services

I like to think there's at least one layer of separation between the core API of the app and an externally facing web-service for things like sanitation, security, JSON decoding, etc that the core API shouldn't need to worry about on every call. It also possible for there to be aspects of the core API that should not be externally facing. However if you've got all your Model/Controller code in your Foundset/Record scopes, writing a web-service is "as simple as"(tm) writing lightweight wrappers around the parts of the core API that you want to expose.

david wrote:4- core code is organized in one spot
5- code completion
6- inline code documentation

All of this is still true if you have Foundset and Record scopes....kind of. You have one place to deal with code for collections of things, and one place to deal with code for a specific thing. I would like to assume that were Record scopes implemented they would have code completion and inline code documentation the same as the current Entity Scopes.


That all being said, I like what you've done with your code. It seems like a great way to solve the problem of there not being a correct place for all types of code. Servoy 6 goes a really, really long way to correcting this, I just don't know if it's all the way there.

Previously we had
Forms: Interface/View logic goes here. You also might want to have a form per table that houses functions that deal with that type of record. Or maybe if you have a lot of code for a table you'll need a couple of forms.
Globals: Utility code, generic code, anything that needs to be accessed across all modules goes here. Code completion is going to suck.

Now we've got
Forms: Interface/View code. Pure, simple, awesome.
Scopes: Utility/Generic code clustered into logical groups. Code completion thanks you.
Globals: Things that somehow don't fit into scopes.
Entity Scopes: Code for dealing with collections of your types of records.

Leaving us with the problem of where to put code for what individual records/objects can do.

All in all, it's a great change, and I'm guessing that if pressed I'd admit that having a scope per record type wouldn't be a terrible thing
Code: Select all
scopes.task.setIsComplete(rTask, true)
, but, I mean.....it just doesn't feel right, you know?
Programmer.
adBlocks
http://www.adblocks.com
jgarfield
 
Posts: 223
Joined: Wed Sep 28, 2005 9:02 pm
Location: Boston, US

Re: Record level Entity Scopes

Postby david » Wed Oct 03, 2012 12:00 am

jgarfield wrote:Now we've got
Forms: Interface/View code. Pure, simple, awesome.
Scopes: Utility/Generic code clustered into logical groups. Code completion thanks you.
Globals: Things that somehow don't fit into scopes.
Entity Scopes: Code for dealing with collections of your types of records.

Leaving us with the problem of where to put code for what individual records/objects can do.


This is a great way to put it and I agree with you that it is a good improvement. Entities gives you a nice place to do the traditional MVC thing, scopes gives you a cleaner way to build simple APIs, and a record level scope would be a nice thing to add to all this. However, this pretty picture breaks down pretty fast when you consider large multi-module solutions. (Or is entirely an incomplete solution to a much greater issue with code organization.)

First, some straight up limitations/dislikes:

1- Access to entities is the same as calculations which I find to be a little out of context. Would like to see both under a module > datasources node. The entity sheet would show up as a node item just like form method editors. The files are stored this way -- I keep the Navigator pane handy to access in this fashion.

2- Scopes don't allow for multilevel API nodes unless you set up your API the same way I showed above. If you could group scopes in unlimited fashion and depth now that would be something. So this is why we put our API object into the globals scope. We sometimes then use various named scopes to organize certain nodes of code of our main API object.

3- You still have to manage name-spacing yourself.


Tip for entity methods if you go that route: put them in a "resource" module along with all relations and value lists. Include this module in your various interface and add-on modules for whatever those resources are applicable.


Now let's consider a large multi-module solution -- a typical solution that has our business application framework modules, resource modules, modules that provide plugin functionality, modules that extend plugin functionality, and finally specific customized business modules:

modules.png
modules.png (73.9 KiB) Viewed 1802 times


Now add in the task of updating and improving a large majority of these modules across many different client projects without breaking their entire solution or messing with their custom business modules.

Now add to the mix many developers using your code.

Finally, let's take into account that not only other Servoy modules will be accessing a particular module's functionality -- but increasingly other systems and apps outside of Servoy (iOS apps, websites, reporting solutions, etc).

What are the challenges now?

1- Access points: how easy is it for another developer to find methods?
2- Discoverability: how easy is it for another developer to figure out what methods do what?
3- Interoperability: how well do these methods work in another context?
4- Code versions: will a method call break down the line because it is not longer there or the name changed?

I would posit that now your clean org chart is a liability if it is the only way you organize your code. The only way to solve these challenges is with a single access point API and lose coupling achieved by passing context and parameters to API methods. Within a module, our API may reference methods in various areas of the module (scopes, entities, forms, etc), but the API is responsible for organizing everything.

Because of the API mediator: a developer references all functionality from one place and with the same style (ie, globals.QB.etc); it is easy to step through the API object via code completion to discover the available functionality; interoperability is accomplished by passing context and parameters to functions; and code versioning and legacy support can be accomplished without having to change any calling methods so things don't break down the line (this is pretty cool...).

This API concept has been so powerful for us that we now design all new modules starting with the API first. Not the models, not the interfaces -- what is the functional structure of the module. Because this is in place first, we then dogfood the API when we build the models and interfaces. So even within the module we call the API functions to get stuff done as much as possible instead of calling methods directly. This bulletproofs the API functionality, gives us one place to document the module, and we don't switch coding style when we call the API from other places.

Summary

I'll take my:

Code: Select all
globals.someAPI.recordFunctionsNode.setIsComplete(rTask)


over your

Code: Select all
rTark.setIsComplete(true)


any day. Not because it's cleaner code or OOP conforming. Because it simplifies how many developers, many modules, many solutions -- all work together with the least amount of friction. This is the goal of good code organization in our world. Moving to this API concept literally released us from code hell.
David Workman, Kabootit

Image
Everything you need to build great apps with Servoy
User avatar
david
 
Posts: 1723
Joined: Thu Apr 24, 2003 4:18 pm
Location: Washington, D.C.

Re: Record level Entity Scopes

Postby david » Fri Oct 19, 2012 4:36 pm

David Workman, Kabootit

Image
Everything you need to build great apps with Servoy
User avatar
david
 
Posts: 1723
Joined: Thu Apr 24, 2003 4:18 pm
Location: Washington, D.C.


Return to Discuss Feature Requests

Who is online

Users browsing this forum: No registered users and 5 guests