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 (73.9 KiB) Viewed 10924 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.
SummaryI'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.