Copy link to clipboard
I've been reading a lot about object oriented programming (OOP) and domain driven design (DDD) over the past few months. I've also been trying to wrap my head around how I can implement OOP coding styles into my daily CFML application development.
Most of the time you'll see OOP/DDD applied towards solving rather complex problems, which makes sense in terms of using it for production purposes. However, when trying to learn a concept sometimes it makes sense to take something simple and make it a little more complicated in order to learn. In other words, I need to learn how to walk before I can run.
What I've done is set up a simple model for a user registration system that will be used on a web site. I'm sure we've all built tons of these things - a user visits a web site where they are asked to enter their name, email address, password, etc... My goal was to take what I've learned about OOP so far and apply it to a scenario like this.
In my reading about OOP I came across a concept called an "anemic domain model" which some people consider to be an anti-pattern. An anemic domain model, as I understand it, is basically using your entity objects (the "user" object in this case) as dumb "bags of getters and setters". Essentially this means your objects don't contain any business logic and aren't all that different different than a standard CFML struct. Many people argue that entities should contain behavior. The difficult part for me is understanding which behavior belongs in an entity, and which belongs in a service. If you add too much behavior in an entity, you could wind up with a massive object that is trying to do too many things at once (a "no-no" if you're following SOLID principals). Walking that thin-line has been a challenge for me.
Another thing I came across (special thanks to Hal Helms and Sean Corfield) that really struck a cord with me was that an entity object should never be allowed to exist in an invalid state. I really liked that idea and felt that if I could ensure my entities were never invalid, I could write better code with fewer potential for bugs. My takeaway from this concept is that from the time you create an entity object to the time it evaporates at the end of a request, it should never ever exist in an invalid state. For example, if you had a business rule stating that all users must have an email address. You would need to design your User entity so that emailAddress could never be anything other than a valid email address.
So how does all this apply when you're talking about a user registration form? Here's a diagram I made of my model:https://i.imgur.com/NoxX0Eh.png
You might be asking yourself right about now, "Why do we even need a separate object to hold the form data? Couldn't we just add the data directly to the user entity object?" The reason we use the RegistrationForm object is because a registration form can exist with no data, partial data, or even drastically invalid data. Think about it... when you first visit a registration form all of the text boxes are empty. Is the form in an invalid state? No. You can type anything you want into the name fields, or you could leave them blank. You could type gibberish into the email address field, or make a password with only 1 character. The form, still exists without throwing an exception error. In other words, a form is simply something that accepts user input.
If you tried to add a gibberish email address into the User entity object the User entity object would exist in an invalid state. That would be very bad. To prevent invalid states from occurring gracefully, the RegistrationForm entity has some business logic thrown into it, the validate() method. The validate() method checks to see if the data contained within itself is suitable to be placed within a User entity object. If the validation passes, the User entity is updated. If validation doesn't pass, the form shows itself to the user again so they can correct their inputs.
Here's a diagram showing how that process works:
So there's my attempt at taking something as simple as a user registration form and applying my understanding of OOP principals. I'd love to hear your thoughts on my approach, how you'd handle things differently, and if you have any suggestions for me moving forward.
One thing that I'm still wrestling with is the idea of the "anemic domain model" and how much (and what kind) of behavior to include in my entities. I can think of some examples where an object might have a bunch of behavior or business logic and it would make sense to break that out into several different services otherwise your entity object code would be massive and unmanageable.
Thanks for reading and I look forward to hearing your thoughts and criticisms.
Copy link to clipboard
As you seek and explore more on this, I’ll point out that there’s an entire book on the topic, “Object-Oriented Programming in ColdFusion” by Matt Gifford. While written in 2010, most concepts should not have changed much since then:
That should be one long line. If it’s appearing broken into two.
I can’t comment myself beyond that, regarding all that you shared. OO/COD/SOD is just not my bag. I focus solely on CF server troubleshooting, not having done professional CFML development for about 15 years. I just wanted to point you to the book in case perhaps you find that no one else replies with more.
Hope it’s helpful.
Thanks for the reply, Charlie. It's funny you mention the Matt Gifford book as that is the first ColdFusion OOP book I read. The Amazon link didn't work for me but you can also find it on PacktPub here. I then followed up with reading the Adobe ColdFusion Anthology by Michael and Judith Dinowitz which covers many topics including some OOP patterns however I didn't feel that book went into as much detail as I would have liked on the subject of OOP.
I haven't found many modern resources or sample code for OOP/DDD development with CFML these days so I've turned to looking at examples for other languages like Python or Java.
Copy link to clipboard
A laudable undertaking. I don't want to split hairs, just to make an additional remark or two.
What you've presented (domain objects, entities, behaviour, responsibilities, S.O.L.I.D. principles, etc) is OOAD, Object-Oriented Analysis and Design. This is different from OOP, Object-Oriented Programming, which comes later (encapsulation, polymorphism, inheritance, abstraction, packages, etc).
I wouldn't worry about dogma. I would just make sure my design has the minimum set of classes necessary to fully model my domain. Each such class would have a specific responsibility. It would contain just the properties (fields) and behaviour (methods) it needs to carry out its responsibility.
Its properties should be private by default. Any client who wishes to know the state of an object, or to change it, has to do so by means of a public interface. That is where the getters and setters come in. But they are by no means obligatory.
Now, if you were to switch databases from SQL Server to MySQL or Oracle, would it mean changing huge portions of your design or code? If so, then your domain and data layer are strongly coupled. You should use Data Access Objects to mitigate such coupling. They would do so by enabling your application to seamlessly switch database brands, without any significant change in design or code.
Sorry for the delayed reply BKBK. Thank you for sharing your thoughts on the subject and I think you're right. It's very easy when learning about OOP (or OOAD) to get overwhelmed with terminology and best-practices which are often misused on Stack Overflow and various programming blogs. There definitely is a lot of programming Dogma out there, and I find myself struggling with which ideals to put into practice, and which I should leave out.
One thing I've started using in my apps since I initially wrote my first message is the repository pattern. Even though adding a repository has added additional complexity to my project, I like the idea of adding an abstraction layer between my business logic and the database access objects. I believe that utilizing the repository also does address the concern you brought up about coupling data access with the rest of the application.
I actually designed a chart that I keep nearby that I use for reference when planning out an app. The chart is designed to identify the different "layers" of an application and where each type of object should live and who it should communicate with.
Here's the chart I've been using in case anyone is curious or has feedback:
[caption width="999" align="alignnone"] Application layer reference chart[/caption]