CFML Slack
January 14, 2016
Unrelated messages omitted for brevity.
Punctuation and spelling not touched.
tbrown [11:14 AM]
looking for general feedback/resources in making sure the model layer of my application is structured in a standard way that will produce a solid service layer. Is there a good resource as to what should be included in the service layer (simple api that invokes a DAO layer, etc? I understand this is a highly subjective question, but was curious if anyone had some examples of how they create their service/dao layer so I can improve my code organization and re-usability.
aaronstoddard [11:15 AM]
http://objectorientedprogrammingincoldfusion.com/ <-- A good book for that purpose. Object-Oriented Programming in ColdFusion by Matt Gifford
Are you using any frameworks? Using Dependancy Injection?
For my own projects, My model folder is divided into feature folders: /model/authentication/authenticationObj,authenticationGateway,authenticationService
tbrown [11:18 AM]
yeah it's a coldbox application, but figured the question might be more broad reaching so thought i'd throw it out here..
tbrown [11:22 AM]
i'll definitely pick up a copy of that book, but is there are general rule of thumb that the service only knows about other services and the gateway is only ever invoked by the service of the same feature?
aaronstoddard [11:23 AM]
As OOP pattern that is the generally accepted method.
Gateway (and/or DAO) is specific to its objects data persistence layer.
aaronstoddard [11:26 AM]
So only the object knows its gateway functions(if you inject the gateway into your object) or you could inject your gateway into your object specific service and have the service manages the interaction between the object and the gateway.
tbrown [11:33 AM]
@aaronstoddard: thanks.. I think I'm on the right track, just trying to get my mind around the best practices... so the service layer needs to stay as generic as possible to make the api the handlers (and other services) use be as agnostic as possible. Just didn't know if there was a way to structure my model to accomplish this with only a "bean" and a service. It sounds like if I add the gateway layer this allows my service to be more of a proxy for the business logic which further hides that complexity from the consuming application.
bdw429s [11:38 AM]
@tbrown: You're going to want some kind of data access layer unless you're using ORM. Gateway is really more of a CF-thing with the initial though that your DAO returned a single object, but the gateway was for multuiple records. I never bothered with the distinction and always used DAO in my non-ORM projects.
The really only hard-fast rule is to find what works best for you and your team, and creates a well-organized and manageable code base. _Always_ keep in mind that is your prime directive.
My second rule of thumb was
Fat beans, thin services, and thinner controllers.
Creating and testing your model objects first before ever wiring it into a handler forces you to keep that business logic where it belongs.
tbrown [11:42 AM]
@bdw429s: thanks.. i think that makes sense. Like you said this highly dependent on what works best for the particular project, but when you say Fat Beans what would be an example of extra methods that you would add to the bean? getFullName() to concat fname and lname? and then in your service do you just inject wirebox and create the DAO object in the service? maybe i'll :redirect: to #box-products
miguel-f [1:07 PM]
@bunniez: In case you have not seen it, learn CF in a week is probably the best (only?) tutorial out there for CFML. http://www.learncfinaweek.com/ Learn CF in a Week is a OpenSource ColdFusion training course that teaches people everything they need to know to be a ColdFusion developer
bunniez [1:21 PM]
@miguel-f: thanks, i did poke through that a bit, typically my job keeps me so occupied that i'm kind of learning as i go between online toots and the manuals.
seancorfield [1:22 PM]
@tbrown: Yeah, that whole DAO/Gateway separation thing was probably "my fault" as I introduced it in "intro-to-OOP" material for CF about 10-13 years ago, thinking the "rule" of separation would help CFers migrate to OOP more cleanly (keep queries in the Gateway, keep single Object persistence in the DAO). But a lot of people treated it as gospel rather than just a starting point. And stuff like Illudium P-38 solidified it. And then we had the 5:1 anti-pattern all over CFML code 😦
bdw429s [1:24 PM]
@seancorfield: I still remember your article on the anemic domain model in the fusion quarterly :simple_smile:
tbrown [1:24 PM]
Thanks @seancorfield do you have an example of what you would recommend instead?
seancorfield [1:25 PM]
The best advice is that Fusion Authority article but I don’t have the source (or the rights to republish it).
bdw429s [1:26 PM]
Man, I used to have a copy of it...
tbrown [1:26 PM]
Just trying to get away from the 5:1
seancorfield [1:29 PM]
FYI http://corfield.org/entry/The_DAO_and_Gateway_separation_in_CFML_is_nonsense (from March 2009)
And the background to it https://groups.google.com/forum/#!msg/cfcdev/WsyNC-umibE/AxSAtVOafeMJ
You can read a teaser of the Fusion Authority chapter here: http://link.springer.com/chapter/10.1007%2F978-1-4302-7214-4_21#page-1
Beans and DAOs and Gateways, Oh My! When you decide to incorporate object-oriented programming and design patterns into your ColdFusion toolbox, the most confusing set of concepts is the whole notion of “beans and DAOs and gateways and
clockworkrebel [1:36 PM]
What is the 5:1 antipattern?
seancorfield [1:37 PM]
Bean + DAO + Gateway + Service + Controller for every single domain object.
clockworkrebel [1:37 PM]
Ah
seancorfield [1:37 PM]
So if you have 20 DB tables, you’d end up with an app that has close to 100 CFCs 😦
I’ve seen that in production code… horrific...
I actually saw a Mach-II app that pretty followed that anti-pattern and it had 200 database tables OMG!!
clockworkrebel [1:40 PM]
I'm sorry to sound like a child here, but what's a bean? Is that the same as or similar to a "javabean"?
tbrown [1:41 PM]
@seancorfield: thanks for the links I had read them and made me realize i needed to do something different...so i guess i still have a lot of reading to do, but what are the layers you are advocating instead? bean (object centric vs table) and DAO for interacting with a grouping of beans and services that bring the logic together into a larger unit of related functionality?
seancorfield [1:42 PM]
Turns out I have the raw source of the article here http://corfield.org/articles/beans_etc.pdf — no images, just text / unformatted.
clockworkrebel [1:42 PM]
oh maybe I should read those links =P
seancorfield [1:42 PM]
Not sure how valuable that is without the illustrations.
@clockworkrebel: loosely a "bean" is most often a "domain object" but in the context of a "bean factory" (dependency injection a.k.a. inversion of control) all managed objects are "beans"… some are transient (usually the "domain objects", which also have a persistent representation in the database) and the rest of singletons (services of various sorts).
If those terms don’t help clarify then I’ll suggest Google for the basics...
clockworkrebel [1:46 PM]
thank you @seancorfield I'm reading your linked pdf right now. I believe we use these things where I work, but perhaps don't use the right terminology
clockworkrebel [2:09 PM]
wow that article was really interesting, and now I have some stuff to think about
tbrown [2:15 PM]
@seancorfield: so if understand what your article is saying.. Our beans (business objects) should be more than just simple data containers for a single row in a table, but instead be enriched with logic that manipulates its own data. Then our data access objects should be more generic and not so tightly coupled to a single table but ecapsulate the data access layer (typically SQL) for acting on the data stored in functionally related business objects. And services are used to provide a more generic facade to our data gateways?
clockworkrebel [2:19 PM]
I got the idea that the DAO would be tightly coupled with the underlying table(s) but that this was ok, because the business objects could in a sense stand on their own, and if you had a change to the way data was stored, you would only have to rewrite (or modify) your DAO
but maybe I misunderstood
seancorfield [2:22 PM]
@tbrown: In an "ideal" OOP system, your domain objects contain nearly all of your business logic — "service" objects exist only to orchestrate operations across multiple domain objects.
tbrown [2:23 PM]
no i likely didn't phrase my summary very well.. i think you are correct.. but the key is to stop thinking about tables and think about business objects.
seancorfield [2:25 PM]
Database tables are "just an implementation detail" of how objects are persisted. A domain object might actually be stored in multiple related tables. Similarly, a single DB table might actually contain multiple types of domain object.
tbrown [2:27 PM]
so it's ok to think of a domain object (bean) as having many properties that span multiple tables.. then when i pass it a rich domain object the DAO layer splits that object into it's underlying homes in the db..
likely just rephrased what you just wrote but trying to wrap my head around this giant 5:1 explosion and reduce the amount of objects I have so I can begin to think more about the business logic of it rather than my objects being a direct mapping to my database tables..(edited)
clockworkrebel [2:30 PM]
Now that i'm thinking about it, it's really important that the DAO be able to split a domain object into many tables. Imagine the terrible schemas that we would come up with if the penalty for creating another table was to have to create 4 or 5 new components!
Thinking about it further, I guess that's what this is all about, and in a sense, why this 5:1 antipattern is in fact an antipattern
So maybe that was more of a platitude than a realization =P
jtreher [2:38 PM]
You can also imagine that you are using a relational storage system such as oracle and then you for some reason are able to switch to a flattened document store such as Mongo for this particular object's persistence. You should still be able to call datalayer.save() from within your business object. If you had it setup correctly, you would not have much to change.
tbrown [2:41 PM]
so you call
datalayer.save()
from within the business object (bean)? so you are injecting the data layer into the bean? I'm sure i'm being obtuse.. too many trains of thought
jtreher [2:42 PM]
Yes, you can inject the data access object(s) and services into the bean. It's pretty awesome.(edited)
All of Sean's hard work.
seancorfield [2:43 PM]
Our pattern tends to be
myDomainObject.save()
and haveDomainObject.cfc
depend on the persistence service so it can do:function save() { variables.persistenceService.save("DomainObject",this); }
clockworkrebel [2:44 PM]
Yeah I guess the idea is that the bean needs to be able to save itself. And it's allowed to know that it has a datalayer, just not how that datalayer works
seancorfield [2:44 PM]
A controller would typically need to call a service to domain objects to work on, but then could just tell them to save themselves when the controller is done.
At World Singles, we have a data mapper that transparently deals with both MySQL and MongoDB so our application code doesn’t need to know how domain objects are actually stored.
(I published most of the data mapper on GitHub)
tbrown [2:47 PM]
k. i'll check it out, definitely have been hunting for good examples of this design pattern so I can overlay it onto my business logic.. appreciate the help.
tbrown [3:02 PM]
are there any other good examples of this somewhere on git hub unless i'm missing it in the datamapper repo.
Our pattern tends to be
myDomainObject.save()
and haveDomainObject.cfc
depend on the persistence service so it can do:
seancorfield [3:50 PM]
https://github.com/seancorfield/datamapper/blob/master/Bean.cfc#L272-L288
Specifically https://github.com/seancorfield/datamapper/blob/master/Bean.cfc#L277
Note that we track dirty vs clean fields so we can run an update on just the dirty fields.
All of the MongoDB/MySQL magic happens in
WS.data.save_row()
— which is written in Clojure but could have been written in CFML (it would just be slower in CFML).
tbrown [4:02 PM]
so your domainObject extends the Bean.cfc?
i reread the readme.md i think it's making more sense.. appreciate the patience on this one.. trying to put together a quick example app outside my larger app to test out these concepts so I can grok everything
seancorfield [4:18 PM]
Yeah, sorry, it’s all very generic. If we need anything beyond bare persistent data, we have a
foo.cfc
that extendsBean.cfc
and have all the domain logic in that child component.
Similarly, we have
BarService.cfc
that extendsBaseService.cfc
to provide common, low-level persistence operations for the core type manipulated by the server.
tbrown [4:22 PM]
appreciate the help and i understand a lot of the answers are it depends, just trying translate this to User.cfc UserDAO.cfc and and User.cfc would have the DAO injected into the bean via wirebox. Could you explain the
.save("DomainObject",this);
this represents the current domain object and the quoted portion is just so thesave()
method knows what type of objectthis
is?(edited)
jtreher [5:03 PM]
You are in deep now! It's some kind of magical bean. The easiest way to learn it is to explain it to your coworker with a working example.
seancorfield [5:04 PM]
"and the quoted portion is just so the
save()
method knows what type of objectthis
is?" — yes, at a basic level. If you have a generic data layer, when it is passed the data to be saved, it also needs to know how/where to save it.
There are several possibilities. You could, for example, have
bean.getTableName()
always defined and have the data layer call that (in which case you don’t need to pass it in, just the bean reference itself).
But then why would other code need/want to know/access the table name for a given object?
So the approach we’ve taken is to explicitly tell the data mapper where to store/load the data (which we keep in
variables.name
— and we don’t use standard get/set functions — ours are smart enough to know aboutvariables.data
(original, read-only data, loaded from the data store) andvariables.dirty
(writable, updated data, changed since last load/store operation).
so, for us,
bean.getFoo()
is handled byonMissingMethod()
which callsbean.get("foo")
which by default checksvariables.dirty
for a recent value else checksvariables.data
for the original value.
Similarly,
bean.setFoo(blah)
routes thrubean.set("foo",blah)
which doesvariables.dirty["foo"] = blah
It’s actually a little more complex than that (we track the clean/dirty state of an object and we also record whether an object is empty or "loaded" with data)… and all our beans can act as iterators over arrays of structs as well as just a single object wrapped around a struct.
tbrown [6:36 PM]
Thanks Sean. But if I can't abstract it that much or just want the control on a per object level it's perfectly acceptable to have a DAO for each object or is that trending back to the antipattern?
seancorfield [6:38 PM]
I would probably try to stay away from the 1 DAO : 1 Domain Object setup and have a Data Gateway (or Data Access Object — they’re really just different names for the same concept) for each group of closely related Domain Objects.
So maybe a UserDataGateway would know how to saveUser(), loadUser(), updatePermissions() and so on.(edited)
The reason for that is that when you ask the Gateway for queries / reports you’re likely to be working with a related set of tables (user, permission, group, etc).
So this keeps the conceptual domain language of your problem space consistent with your code organization
tbrown [6:41 PM]
Ok thanks.. And the userDataGateway is injected into the bean? For things like 'dao.saveUser( this );'
seancorfield [6:45 PM]
Yup… so
UserDAO.cfc
orUserDataGateway.cfc
orUserGateway.cfc
… whatever you prefer. And the appropriateproperty
declaration in theUser.cfc
bean.
(assuming you’re using a DI/IoC framework)
tbrown [7:08 PM]
Yes (wirebox). Thanks for the back and forth on this. Very helpful!
seancorfield [7:14 PM]
Always happy to talk about architecture, OOP, and FP stuff!
Great chat, thanks for sharing this!