Models
|
Model Integration Guide
Introduction
Model integration helps you create, manage, and use model (business logic) objects very easily within your ColdBox application. The model integration helps you even manage model dependencies very easily via our own dependency injection language and conventions. You will find that the model integration can also be used alongside object caching or even object factories like ColdSpring and LightWire. However, the main purpose for model integration is to make developer's development workflow easier! And we all like that Easy button! This integration will give you a good kick start on dependency injection, caching, persistence, etc without you actually studying for it. Some very simple conventions are all you need to get you started. Now, what does model integration do for you:
- Easily create and retrieve model objects by using one method: getModel() from handlers, plugins and interceptors.
- Easily handle model dependencies by using cfproperty and constructor argument conventions. In other words, we have our own dependency injection framework based on conventions.
- A conventions DSL (Domain Specific Language) has been created to facilitate what needs to be injected in the models (Don't shiver with fear yet, please keep reading)
- Persistence: Use the same rock solid ColdBox cache to persist model objects by using cfcomponent cache metadata. You can now have services that can adjust according to available memory.
- Easily create model mappings or aliases for any model class.
- Easily populate model objects with data from a request: populateModel()
Wow! You can do all that? Yes and much more. So let's begin.
Model Layer Overview
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. You can even create the entire model layer in another language or physical location (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.
A simple example can be described like so. 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
Very straightforward right? Anyways, 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
- Book.cfc
Service Layer
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. by 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. I won't deep into service layer design or approaches as there are various considerations and opinions on what exactly to put on them or how to layer them. 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 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 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.
Now, if I know that my database operations will get very complex or I want added separation of concerns, I could add a third class to the mix: BookGateway.cfc that could act as my table 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 when needed. 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.
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...
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 it. 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.
Conventions Location
All your model objects will be located under your model folder of your application root. This is a convention and can be changed if you so desire by updating the modelsLocation element in the Conventions element of your configuration file. (See the [wiki:cbConfigGuide Configuration Guide]) You can also change it for the entire framework installation in the coldbox settings file: coldbox/config/settings.xml. (See Conventions)
ColdBox Configuration File:
<Conventions> <handlersLocation></handlersLocation> <pluginsLocation></pluginsLocation> <layoutsLocation></layoutsLocation> <viewsLocation></viewsLocation> <eventAction></eventAction> <modelsLocation></modelsLocation> </Conventions>
Models External Location
You can also have an external location for your model objects by using a ColdBox setting:
- ModelsExternalLocation : This setting is the base instantiation path of the model folder.
<Setting name="ModelsExternalLocation" value="coldboxlibrary.models" />
This gives you the ability to create centralized locations for model objects that you can easily bring in to your applications.
Important: Model objects in your conventions take precedence over the external location.
Model Configuration Options
There are also several other ColdBox settings that deal with Model Integration. Below is a nice chart of all the settings you have available for Model Integration.
| Setting | Type | Default Value | Description |
|---|---|---|---|
| ModelsObjectCaching | boolean | true | Tells the bean factory to cache model objects if cache metadata is found |
| ModelsSetterInjection | boolean | false | Use setter injection alongside metadata injection |
| ModelsDebugMode | boolean | false | Logs model creation and injections |
| ModelsDICompleteUDF | string | onCompleteDI | The global name of the UDF to call after injections (if found in cfc) |
| ModelsStopRecursion | string (list) | --- | A comma-delimmitted list of class names where the factory should stop recursion. Ex: transfer.com.TransferDecorator |
| ModelsExternalLocation | string | --- | The base instantiation path of where external model objects can be located |
<Setting name="ModelsObjectCaching" value="true" /> <Setting name="ModelsSetterInjection " value="false" /> <Setting name="ModelsDebugMode" value="false" /> <Setting name="ModelsDICompleteUDF" value="onDIComplete" /> <Setting name="ModelsStopRecursion" value="transfer.com.TransferDecorator,model.base.BaseService" /> <Setting name="ModelsExternalLocation" value="externallibrary.models" />
Usage Methods
The following usage methods are available in all handlers, plugins and interceptors. The getModel() method is also available for all unit testing classes and the coldbox proxy.
- getModel(string name, [boolean useSetterInjection=false], [string onDICompleteUDF=onDIComplete], [boolean debugMode=false])
- populateModel(any model, [string scope=none], [boolean trustedSetter=false],[string include=],[string exclude=])
The getModel method has 4 arguments:
| Argument | Require | Default | Description |
|---|---|---|---|
| name | true | --- | The name or alias of the model object |
| useSetterInjection | false | false | Defaults to false, you can turn it on, to do both setter injection and mixin injection. |
| onDICompleteUDF | false | onDIComplete | Defaults to onDIComplete. This means that if the object has an onDIComplete() method, it will be called after the object has been created and all dependencies have been injected to it. |
| debugMode | false | false | Defaults to false, you can turn it on and it will log aout creations and dependencies in your log file. |
| stopRecursion | false | --- | A comma delimmitted list of class names that the factory should stop the recursion looking for dependencies on. |
The populateModel has 5 arguments:
| Argument | Require | Default | Description |
|---|---|---|---|
| model | true | --- | The name/alias of a model object or an actual instantiated object to populate. |
| scope | false | --- | If a scope is sent, then the bean factory will populate the variables that match the desired scope name with the request ollection name. Great if you do not want to expose setter methods. |
| trustedSetter | false | false | Defaults to false, this flag tells the bean factory to call the setter methods without checking if the setter mehod exists. Great for using implicit setters or onMissingMethod setters. |
| include | false | A list of keys to include from the public request collection when populating. | |
| exclude | false | A list of keys to exclude from the public request collection when populating. |
<cfset var oUser = getModel('User')> <cfset populateModel(oUser)> or <cfset var oUser = populateModel("User")>
Dependencies DSL
We have created a nice DSL for dependency injection via cfproperty, function and argument markers. This is just an extension to what we have already since the 2.0X series started. Not only does the DSL types apply to model objects but to anything that is autowired in ColdBox: plugins, handlers, interceptors, ioc produced beans, on demand autowiring and now model objects. See the Autowire Guide. This section is important because this is how you can wire up your model objects with the dependencies they need. However, it will also show you how to wire up these model objects in your handlers, plugins and interceptors. Below is the cfproperty definition:
CFPROPERTY
- name : The name of the property to be injected
- type : The type of property to inject (see chart)
- scope : Into which scope to inject the object/setting to. Defaults to variables scoppe
Type DSL The following DSL is how you explain to the framework what dependency you want.
| Type | Description | |
|---|---|---|
| ioc | Get the named ioc bean and inject it. Name comes from the cfproperty name or argument name | |
| ioc:BeanName | Get the ioc bean according to Bean Name in DSL | |
| ocm | Get the name key from the ColdBox cache and inject it. Name comes from the cfproperty name or argument name | |
| ocm:ObjectKey | Get the object from the ColdBox cache according to DSL object key. | |
| model | Get a model with the same name or alias as defined in the cfproperty name="{name}" attribute. Name comes from the cfproperty name or argument name | |
| model:{name} | Same as above but it will get the {name} model object from the DSL and inject it. | |
| model:{name}:{method} | Get the {name} model object, call the {method} and inject the results | |
| webservice:{alias} | Get a webservice object using an {alias} that matches in your coldbox.xml | |
| coldbox | Get the coldbox controller | |
| coldbox:setting:{setting} | Get the {setting} setting and inject it | |
| coldbox:plugin:{plugin} | Get the {plugin} plugin and inject it | |
| coldbox:myPlugin:{MyPlugin} | Get the {MyPlugin} custom plugin and inject it | |
| coldbox:datasource:{alias} | Get the datasource bean according to {alias} | |
| coldbox:configBean | get the config bean object and inject it | |
| coldbox:mailsettingsbean | get the mail settings bean and inject it | |
| coldbox:loaderService | get the loader service | |
| coldbox:requestService | get the request service | |
| coldbox:debuggerService | get the debugger service | |
| coldbox:pluginService | get the plugin service | |
| coldbox:handlerService | get the handler service | |
| coldbox:interceptorService | get the interceptor service | |
| coldbox:cacheManager | get the cache manager |
Note: The model integration feature supports multi-levels of inheritance. The internal bean factory will inspect all the cfproperties and setter methods throughout the iheritance chain.
Constructor & Setter Dependencies
You can easily use the mentioned DSL to wire up a model object's constructor method init() by placing a marker annotation on the argument. For setter methods, you place the marker in the setter method and not the argument. The default attribute is called: _wireme. So a simple example would be the following:
<--- Constructor Markers ---> <cffunction name="init" returntype="any" output="false"> <cfargument name="dsn" type="any" _wireme="coldbox:datasource:myDSN" /> <cfargument name="orm" type="transfer.com.Transfer" _wireme="ocm:Transfer" /> </cffunction> <--- Setter Markers ---> <cffunction name="setTransfer" type="transfer.com.Transfer" output="true" _wireme="ocm:transfer"> </cffunction>
As you can see, you use the argument or function marker: _wireme to tell the bean factory how to wire up the argument or setter method. Now, if you do not like my default marker, then choose your own. Just create a new setting in your coldbox.xml.cfm named: beanfactory_dslMarker.
<Setting name="beanFactory_dslMarker" value="wireit" /> //Then use the wireit marker: <cffunction name="init" returntype="any" output="false"> <cfargument name="dsn" type="any" wireit="coldbox:datasource:myDSN" /> <cfargument name="transfer" type="transfer.com.Transfer" wireit="ocm" /> </cffunction>
What happens if I don't put a metadata marker?
If you do not place a metadata marker then ColdBox will check if you are using an IOC Framework by looking at the IOCFramework setting. If the setting is used, then it will default the target type to ioc. However, if no IOC Framework has been defined, then the default target type is model. This way, if you know that you are injecting model objects or ioc objects, then just ignore the marker.
Note: The marker is used only to demarcate using the DSL. If not using the DSL, then it will use the IOC or Model defaults accordingly.
As you can see from the samples above, wiring up the constructor argument is fairly easy and very descriptive. You are also not relying on inherited functionality or conflicting code, it is purely metadata that can be ignored if using another factory other than ColdBox.
Injecting Dependencies
Above we learned how to wire up the arguments of an object's constructor. Below you will learn how to wire up dependencies AFTER the object get's created or you are wiring up handlers, plugins and interceptors. So if an object needs dependencies after creation (usually the case), then just use our good old friend cfproperty to demarcate or annotate what needs to be injected. The good thing again, is we just rely on unobtrusive metadata to define what needs to be injected and it can be documented! Ahh how I love that!
<--- Autowire Properties ---> <cfproperty name="myMailSettings" type="ioc" scope="instance"> <cfproperty name="ColdBox" type="coldbox" scope="instance"> <cfproperty name="ModelsPath" type="coldbox:setting:ModelsPath" scope="instance"> <cfproperty name="Utilities" type="coldbox:plugin:Utilities" scope="instance"> <cfproperty name="ConfigBean" type="coldbox:configbean" scope="instance"> <cfproperty name="MailSettingsBean" type="coldbox:mailsettingsBean" scope="instance"> <cfproperty name="MySiteDSN" type="coldbox:datasource:mysite" scope="instance"> <cfproperty name="testModel" type="model" scope="instance"> <cfproperty name="initDate" type="model:formBean:getinitDate" scope="instance">
That is so nice. We can use this DSL to inject almost anything into our model objects. How cool is that! Well, you might be saying, that if I have to give my model a name, what is it. Well it is the path from your model directory to your cfc. Again, you might be saying, what if I refactor, I have to change all the references? Well, the answer is no! We have model mappings.
Model Mappings
Just by creating a modelMappings.cfm in your config folder and calling a simple method from within it, you can create model object aliases. What does this mean? It means you can create an alias name for your object's instantiation path. This will help hide the true class path that can be so essential when refactoring or changing the location of objects. I highly encourage you to do this:
- addModelMapping([alias=defaults to the last item in the path],path)
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
| alias | string | false | last part of the path | A comma delimmitted list of aliases to match to a specific path. |
| path | string | true | --- | The instanatiation path of the model object |
Remember that the path is the instantiation path from the model folder WITHOUT the model folder and WITHOUT '.cfc'. That's it! Just call this method and create alias names for your model objects. What is also better, is that it is a cfm template, so you can get funky and dynamic. You can do if statements, get data from databases, anything you like.
<cfscript> /* Add all the model mappings you want*/ /* addModelMapping(alias="",path="")*/ addModelMapping('MyFormBean','beans.formBean'); //Adding with a list of aliases addModelMapping('SecurityService,Security,MySecurity','security.SecurityService'); </cfscript>
You can also get creative because this is a CF template file and you can even dynamically register all your model objects by doing a directory listing and some creativity. The example above means that I can call the formBean object using the alias or the full path:
- Alias : getModel('MyFormBean')
- Full Path : getModel('beans.formBean')
I prefer the alias approach, as I can refactor my model anyway I want, without affecting my controller code.
Persisting Model Objects
Thanks to metadata and the ColdBox cache, you can use cache metadata attributes and persist your model objects. You can create singletons, transients and even time expired model objects that can adjust to the server's memory demands.
<--- Singleton: Lives for entire application time ---> <cfcomponent name="Model" cache="true" cacheTimeout="0"> <--- Time Expired Object: Object lives for a max of the default cache object timeout in the cache settings ---> <cfcomponent name="Model" cache="true"> <--- Time Expired Object: Object lives for a max of 30 minutes ---> <cfcomponent name="Model" cache="true" cacheTimeout="30"> <--- Time Expired Object: Object lives for a max of 40 minutes, but if not used for the past 15 minutes expire it ---> <cfcomponent name="Model" cache="true" cacheTimeout="40" cacheLastAccessTimeout="15"> <--- Transient: Used on demand ---> <cfcomponent name="Model">
You can create incredible cache sensitive models now, just by tapping into the ColdBox cache. What is also a plus, is that all model object's metadata are internally cached in a metadata dictionary. So creating model objects are FAST!
Important Note: Please remember that the ColdBox cache is based on a solid memory sensitive cache. So objects that have timeouts are not guaranteed that they will live for that long because the JVM can request memory and purge them for you. If this happens, the framework will re-cache the objects a second time seamlessly for you. You do not have to worry about their persistence. It is all done for you.
Simple Example
I will put some simple example code below on how to model a simple User service, gateway, user object and how to use them within a handler. Below is a diagram of our model folder layout.
+ handlers
+ user.cfc
+ model
+ security
+ UserService.cfc
+ UserGateway.cfc
+ User.cfc
Coldbox.xml
This is a snippet of the configuration file:
<Datasources> <Datasource alias="dsn" name="MySite" dbtype="mysql" /> </Datasources>
Model Mappings
I create some mappings so I can refer to them by an alias and not their class path:
//No alias is used, the alias will be the last part of the path. addModelMapping(path='security.UserService'); addModelMapping(path='security.UserGateway');
User Service
This user service is a simple cfc that just has a gateway dependency for complex queries:
<--- Cache of 0 = singleton ---> <cfcomponent name="UserService" output="true" cache="true" cacheTimeout="0"> <--- Dependencies ---> <cfproperty name="UserGateway" type="model" scope="instance" /> <cfproperty name="SessionStorage" type="coldbox:plugin:sessionstorage" scope="instance" /> <cfscript> instance = structnew(); </cfscript> <cffunction name="init" output="false" returntype="UserService"> <cfreturn this> </cffunction> <cffunction name="getAllUsers" output="false" access="public" returntype="query" hint="Returns all users in the database, active and inactive."> <---************************************************************** ---> <cfargument name="orderProperty" type="string" required="false" default=""/> <cfargument name="orderASC" type="boolean" required="false" default="true" hint="Order ASC = true, DESC = false"/> <---************************************************************** ---> <cfscript> var query = ""; query = instance.UserGateway.findUsers(arguments.orderProperty,arguments.orderASC); return query; </cfscript> </cffunction> <cffunction name="authenticateUser" output="false" access="public" returntype="boolean" hint="Authenticate a User. If valid it places them in session. Returns true if user is valid and authenticated and ready for usage."> <---************************************************************** ---> <cfargument name="username" type="string" required="true"/> <cfargument name="password" type="string" required="true"/> <---************************************************************** ---> <cfscript> /* Prepare results*/ var authenticated = false; var oUser = ""; /* Try to get user by credentials*/ oUser = getUserByCredentials(argumentCollection=arguments); //Is User in system. if ( oUser.getIsPersisted() ){ //Save User State instance.sessionstorage.setVar('CurrentUser', oUser); //Set Return Flags authenticated = true; } /* Return Results*/ return authenticated; </cfscript> </cffunction> <--- Get User By Credentials ---> <cffunction name="getUserByCredentials" output="false" access="public" returntype="User" hint="Returns an active/confirmed user by its credentials"> <---************************************************************** ---> <cfargument name="username" type="string" required="true"/> <cfargument name="password" type="string" required="true" hint="This argument is hashed internally."/> <---************************************************************** ---> <cfscript> var oUser = ""; var sqlProps = structnew(); /* prepare sqlProps*/ sqlProps.username = arguments.username; sqlProps.password = hash(arguments.password,'SHA-512'); sqlProps.isConfirmed = 1; sqlProps.isActive = 1; /* Create User*/ oUser = createObject("component","User").init(); /* Try to get user now.*/ instance.userGateway.readByProperties(oUser,sqlProps); return oUser; </cfscript> </cffunction> <--- Get A User Session ---> <cffunction name="getUserSession" output="false" access="public" returntype="User" hint="This method checks if a user is in an authorized session, else it returns the default user object."> <cfscript> var oUser = ""; //Is user in session if ( instance.sessionstorage.exists( 'CurrentUser' ) ){ oUser = instance.sessionstorage.getVar( 'CurrentUser' ); } else{ oUser = createObject("component","User"); } /* Return User Object*/ return oUser; </cfscript> </cffunction> <--- Clean a user's session. ---> <cffunction name="cleanUserSession" output="false" access="public" returntype="void" hint="This method will clean the user session."> <cfscript> instance.sessionstorage.deleteVar( 'CurrentUser' ); </cfscript> </cffunction> </cfcomponent>
User Gateway
This user gateway is a simple cfc that does complex queries on the database for user operations. I separated it into a gateway object, because I plan to have lots and lots of complex queries for users. If you where doing simple queries or an ORM, maybe just having a service layer would suffice. Again, don't think that everything needs a service-gateway combination and especially 1-1 relationships between tables and objects. Remember that objects must have identity and service layers and manage several tables as long as they provide cohesion and well laid out responsibilities.
<--- I will just lay out one method not all ---> <--- Cache of 0 = singleton ---> <cfcomponent name="UserGateway" output="true" cache="true" cacheTimeout="0"> <--- Dependencies ---> <cfproperty name="dsn" type="coldbox:datasource:dsn" scope="instance" /> <cfscript> instance = structnew(); </cfscript> <cffunction name="init" output="false" returntype="UserService"> <cfreturn this> </cffunction> <cffunction name="getAllUsers" output="false" access="public" returntype="query" hint="Returns all users in the database, active and inactive."> <---************************************************************** ---> <cfargument name="orderProperty" type="string" required="false" default=""/> <cfargument name="orderASC" type="boolean" required="false" default="true" hint="Order ASC = true, DESC = false"/> <---************************************************************** ---> <cfset var qUser = 0> <cfquery name="qUser" datasource="#instance.dsn.getName()#"> select* from users order by #arguments.orderProperty# #arguments.orderASC# </cfquery> <cfreturn qUser> </cffunction> </cfcomponent>
Handler Code
This is some handler code for a user handler.
<cfcomponent name="User" output="false" extends="coldbox.system.eventhandler" autowire="true"> <--- Dependencies ---> <cfproperty name="UserService" type="Model" scope="instance" /> <cffunction name="list" output="false" returntype="void"> <cfargument name="event" type="any"> <cfscript> var rc = event.getCollection(); //get all users rc.qUsers = instance.UserService.getAllUsers(); //View event.setView('users/list'); </cfscript> </cffunction> <cffunction name="login" output="false" returntype="void"> <cfargument name="event" type="any"> <cfscript> event.setView("user/login"); </cfscript> </cffunction> <cffunction name="doLogin" output="false" returntype="void"> <cfargument name="event" type="any"> <cfscript> //Authenticate if( instance.UserService.authenticate(event.getValue("username",""),event.getValue("password","")) ){ setNextEvent('user.home'); } else{ getPlugin("messagebox").setMessage("warning","Username and password not valid. Please try again"); setNextEvent('user.login'); } </cfscript> </cffunction> <cffunction name="doLogout" output="false" returntype="void"> <cfargument name="event" type="any"> <cfscript> instance.UserService.cleanUserSession(); setNextEvent('user.login'); </cfscript> </cffunction> </cfcomponent>
Review
You can easily create now a nice domain model and easily use it within your application. The new ColdBox model architecture leverages conventions, caching and a new dependency injection mechanisms by using DSL markers. Enjoy and start building great domain models!
Related Guides
- The ColdBox Configuration File
- Event Handlers
- The ColdBox Request Context
- Layouts & Views
- URL Mapping & Rewriting
- Model Integration
- ColdBox Plugins
- ColdBox Interceptor
- Extending The Request Context
- ColdBox Unit Testing & Integration Testing
- ColdBox Proxy: Powering Flex/AIR/Remote Applications
- ColdBox-Ajax Integration

SideBar
User Login 




Comments (