The Model layer represents your data structures and business logic. The domain-specific representation of the information that the application operates on. Many applications also use a persistent storage mechanism (such as a database) to store and retrieve data. MVC does not specifically mention the data access layer because it is understood to be underneath or encapsulated by the Model Layer. This is the most important part of your application and it is usually modeled by ColdFusion components, CFCs. You can even create the entire model layer in another language or physical location (Java, web services).
All you need to understand is that this layer is the layer that runs the logic show! For the following example, I highly encourage you to also do UML modeling, so you can visualize class relationships and design.
There are tons of great UML resources and tools. Here are some great tools for you:
Sparx Systems Enterprise Architect - http://www.sparxsystems.com/products/ea/index.html
ColdFusion generation templates for Enterprise Architect - https://github.com/lmajano/EA-ColdFusion-CodeGeneration
ArgoUML - http://argouml.tigris.org/
Poseidon UML - http://www.gentleware.com/
Learning UML - http://shop.oreilly.com/product/9780596009823.do
Since some of the examples in this section require the usage of ColdFusion ORM and the ColdBox ORM Extensions module (https://github.com/ColdBox/cbox-cborm), let's use CommandBox to install them in our application in two easy steps:
1. Install Module
2. Add Mapping
Open your Application.cfc
and add a mapping to this module:
The manual addition of the mapping is needed since the ColdFusion ORM loads before anything in our system, so we must tell it beforehand. We cannot use the ColdBox Module CFMappings unfortunately.
ColdBox has a core validation module you can install to provide you with robust server-side validation of model data. You can install it via CommandBox:
You can find the documentation and source in its repository: https://github.com/ColdBox/cbox-validation
Let's say that I want to build a simple book catalog and I want to be able to do the following:
List how many books I have
Search for a book by name
Add Books
Remove Books
Update Books
There are several ways I can go about this and your design will depend on the tools you use. If you use an ORM like ColdFusion ORM you most likely will use either an ActiveEntity approach or build virtual service layers or build a service layer based on our Base ORM services. If you are not using an ORM, then most likely you will have to build all object, service and data layers manually. We will concentrate first on the last approach in order to do our modeling and then build them out in different ways.
I want to apply best practices and use a service layer approach for my application and model design. I will then use these service objects in my handlers in order to do the business logic for me. Repeat after me:
I WILL NOT PUT BUSINESS LOGIC IN EVENT HANDLERS!
The whole point of the model layer is that it is separate from the other 2 layers (controller and views). Remember, the model is supposed to live on its own and not be dependent on external layers (Decoupled). From these simple requirements I will create the following classes:
BookService.cfc
- A service layer for book operations
Book.cfc
- Represents a book in my system
A Service Layer defines an application's boundary [Cockburn PloP] and its set of available operations from the perspective of interfacing client layers. It encapsulates the application's business logic, controlling transactions and coor-dinating responses in the implementation of its operations. Martin Fowler
A service layer approach is a way to architect enterprise applications in which there is a layer that acts as a service or mediator to your domain models, data layers and so forth. This layer is the one that event handlers or remote ColdBox proxies can talk to in order to interact with the domain model. There are basically two approaches when building a service layer:
Designing the services around functionality that might encompass several domain model objects. This is our preferred approach as it creates a more rich service layer and you will not have class explosion.
Designing a service for each business object, which in turn matches a table or set of tables in the permanent storage. This approach is easy to follow, but the consequences are the responsibilities that could be grouped are not and you will end up with class explosion.
The best way to determine what you prefer or need is to actually try both approaches. Once you design them and put them in practice, you will find your preference. I want to concentrate and challenge you to try these approaches out and learn from your experiences. I believe there is NO SILVER BULLET on OO design, just stick to best practices and practice code smell, the art of knowing when your code is not right and therefore smells nasty!
The BookService
object will be my API to do operations as mentioned in my requirements and this is the object that will be used by my handlers. My Book
object will model a Book's data and behavior. It will be produced, saved and updated by the BookService
object and will be used by event handlers in order to populate and validate them with data from the user.
The view layer will also use the Book
object in order to present the data. As you can see, the event handlers are in charge of talking to the Domain Model for operations/business logic, controlling the user's input requests, populating the correct data into the Book
model object and making sure that it is sent to the book service for persistence.
So what can Book.cfc
do. It can have the following private properties:
name
id
createdata
ISBN
author
publishDate
It can then have getters/setters for each property that I want to expose to the outside world, remember that objects should be shy. Only expose what needs to be exposed. Then I can add extra functionality or behavior as needed. You can do things like:
have a method that checks if the publish date is within a certain amount of years
have a method that can output the ISBN number in certain formats
have a method that can output the publish date in different formats and locales
make the object save itself or persist itself (Active Record)
and so much more
Now, all you OO gurus might be saying, why did he leave the author as a string and not represented by another object. Well, because of simplicity. The best practice, or that code smell you just had, is correct. The author should be encapsulated by its own model object Author that can be aggregated or used by the Book object. I will not get into details about object aggregation and composition, but just understand that if you thought about it, then you are correct. Moving along...
Important Your objects are not always supposed to be dumb, or just have getters and setters (Anemic Model). Enrich them please!
Back to the book service object. This service will need a datasource name (which could be encapsulated in a datasource object) in order to connect to the database and persist stuff. It might also need a table prefix to use (because I want to) and it comes from a setting in my application's configuration file. Ok, so now we know the following dependencies or external forces:
A datasource (as an object or string)
A setting (as a string)
I can also now think of a few methods that I can have on my book service:
getBook([id:string])
:Book This method will create or retrieve a book by id.
searchBook(criteria:string)
:query This method can return a query or array of Books if needed
saveBook(book:Book)
Save or Update a book
deleteBook(book:Book)
Delete a book
I recommend you model these class relationships in UML class diagrams to get a better feeling of the design. Anyways, that's it, we are doing domain modeling. We have defined a domain object called Book
and a companion BookService
object that will handle book operations.
Now once you build them and UNIT TEST THEM, yes UNIT TEST THEM. Then you can use them in your handlers in order to interact with them. As you can see, most of the business rules and logic are encapsulated by these domain objects and not written in your event handlers. This creates a very good design for portability, sustainability and maintainability. So let's start actually seeing how to write all of this instead of imagining it. Below you can see a more complete class diagram of this simple example.
Hint Note that sometimes the design in UML will not reflect the end product. UML is our guide but not the final product.
If I know that my database operations will get very complex or I want to further add separation of concerns, I could add a third class to the mix: BookGateway.cfc
or BookDAO.cfc
that could act as my data gateway object.
Now, there are so many design considerations, architectural requirements and even types of service layer approaches that I cannot go over them and present them. My preference is to create service layers according to my application's functionality (Encompasses 1 or more persistence tables) and create gateways or data layers when needed, especially when not using an ORM. The important aspect here, is that I am thinking about my project's OO design and not how I will persist the data in a database. This, to me, is key!
Understanding that I am more concerned with my object's behavior than how will I persist their data will make you concentrate your efforts on the business rules, model design and less about how the data will persist. Don't get me wrong, persistence takes part in the design, but it should not drive it.