Services, DAOs, and Factories - adampresley/cf-basis GitHub Wiki

Services, DAOs, and Factories

Basis offers basic support for your domain model layer. Out of the box there are two base components available for your use to extend: Service and DAO. These are provided mostly as a convenience and offer very little except for an init() constructor method that will copy passed in arguments to the variables scope. The base DAO object additionally offers a dsn argument in its init() constructor method. Despite if you choose to use these base components or not it is still a good practice to organize your code into logical "concerns", each component performing a job specific to a particular part of your domain. Let's look at that more.

Services

Services are the heart of the domain model layer. A service, for this discussion, is a component that handles some part of your domain, and more specifically handles the "business logic". Consider our previous action example earlier in this User Guide regarding contacts. In this sample a contact would be a domain entity, a "thing" in our software. This would lend itself to having a ContactService.cfc component that handles actions against a contact entity (or multiple entities).

This service component would furthermore have an understanding of the business logic and rules that drive working with contacts. So, as an example, if your web application requires that all contacts be put into the back-end database with a Windows NT Active Directory group name your service component can verify that this information is present, and even valid before attempting to insert the contact into the database. Here is a fake version of what such a service could look like.

<cfcomponent extends="Basis.Service" output="false">

   <cffunction name="saveContact" output="false">
      <cfargument name="name" type="string" required="true" />
      <cfargument name="email" type="string" required="true" />
      <cfargument name="activeDirectoryGroup" required="true" />

      <!---
         Pretend we have another service that validates Active Directory stuff
      --->
      <cfset var activeDirectoryService = application.theFactory.getService("ActiveDirectory") />

      <cfif !activeDirectoryService.isValidGroup(arguments.activeDirectoryGroup)>
         <cfthrow message="Invalid Active Directory group" />
      </cfif>

      <cfset application.theFactory.getDAO("Contact").saveContact(argumentCollection = arguments) />
   </cffunction>

</cfcomponent>

The breakdown of this component is pretty simple. It extends Basis.Service which gives us a simple init() constructor method. It provides a method called saveContact() that is supposed to save a contact to a database. In our example we have determined that, as a business rule, all contacts must have a valid Active Directory group, so our method uses another service component (let's pretend it's real) to validate the passed in group. The two new things in this would be application.theFactory.getService() and application.theFactory.getDAO(). More on those down below in the section labelled Factories.

DAOs

A DAO, or a Data Access Object, is a component who's sole job is to handle the interaction with your data persistence layer. This is usually a database of some type, though it certainly does not have to be (web services, anyone?). Much like services in Basis the Basis.DAO component provides a template for creating your components, but you do not have to use it. Let's take a look at a sample DAO component.

<cfcomponent extends="Basis.DAO" output="false">

   <cffunction name="saveContact" output="false">
      <cfargument name="name" type="string" required="true" />
      <cfargument name="email" type="string" required="true" />
      <cfargument name="activeDirectoryGroup" required="true" />

      <cfset var qrySave = "" />

      <cfquery name="qrySave" datasource="#variables.dsn#">
         INSERT INTO contacts (
            name
            , email
            , activeDirectoryGroup
         ) VALUES (
            <cfqueryparam value="#arguments.name#" cf_sql_type="CF_SQL_VARCHAR" maxlength="50" />
            , <cfqueryparam value="#arguments.email#" cf_sql_type="CF_SQL_VARCHAR" maxlength="255" />
            , <cfqueryparam value="#arguments.activeDirectoryGroup#" cf_sql_type="CF_SQL_VARCHAR" maxlength="25" />
         )
      </cfquery>
   </cffunction>

   <cffunction name="getContacts" output="false">
      <cfargument name="nameLike" type="string" required="false" default="" />
      <cfargument name="email" type="string" required="false" default="" />

      <cfset var qryGetContacts = "" />

      <cfquery name="qryGetContacts" datasource="#variables.dsn#">
         SELECT
              c.name
            , c.email
            , c.activeDirectoryGroup

         FROM contacts AS c
         WHERE 1=1
            <cfif len(arguments.nameLike)>
               AND c.name LIKE <cfqueryparam value="%#arguments.nameLike#%" cf_sql_type="CF_SQL_VARCHAR" maxlength="50" />
            </cfif>

            <cfif len(arguments.email)>
               AND c.email=<cfqueryparam value="#arguments.email#" cf_sql_type="CF_SQL_VARCHAR" maxlength="255" />
            </cfif>
      </cfquery>

      <cfreturn qryGetContacts />
   </cffunction>

</cfcomponent>

As you can see there isn't much special about this component, except that it is focused on doing one thing and doing it well. Also because we extended Basis.DAO we can instantiate this component using the init() method, passing in a dsn parameter, making it available on the variables scope.

Factories

Basis does provide a very simple mechanism for service and DAO instantiation through an object called theFactory. This object is available in the application scope automatically and can create any service or DAO that lives in the model directory (configurable) that follows a similar pattern to views.

Let's work with the contacts example again. To use the Basis factory for getting our ContactService and ContactDAO components we created above, we would create a directory under the model folder and name it Contact. We would then place our two components into this directory.

It is important to note that on top of the correct directory structure you must also name your components correctly for the Basis factory to be able to see them. Both your service and DAO components must be named the same as the section directory the live in. So in this example if they live in the Contact folder under model, then your components must be named starting with Contact. Please notice I said "starting with". Service components must be suffixed with the word Service, and DAOs the word DAO. So the above examples would literally be named ContactService.cfc and ContactDAO.cfc.

How would we get an instance of these components in our controller, as an example?

<cfset contactService = application.theFactory.getService("Contact") />
<cfset contactDAO = application.theFactory.getDAO("Contact") />

Next up: The AJAX Proxy

⚠️ **GitHub.com Fallback** ⚠️