Creating a service

A major benefit to creating a service layer is the fact that it creates a simpler, easier interface to interact with and access the data through the larger collection of packages and Class libraries within your code base.

The service layer can also manage the instantiation of related objects.

To highlight the effectiveness of using a Service layer (or Facade Pattern) in your larger applications, the code used in this chapter has grown. We now have not only a component package dealing with User interactions (the Bean, DAO, and Gateway), we also have a package dealing with the user's address, which is available in the complete code download for this chapter.

Let's take a look at the onApplicatonStart() method as it currently stands within the Application.cfc file.

<cffunction name="onApplicationStart" output="false">
<!--- Instantiate the Datasource object. --->
<cfset var objDatasource = createObject('component', 'com.packtApp.oop.beans.Datasource').init( DSName='CFOOP', username='<your dsn username>', password='<your dsn password>') />
<!--- Instantiate the UserDAO object. --->
<cfset var objUserDAO = createObject('component', 'com.packtApp.oop.dataAccess.UserDAO').init( datasource=objDataSource) />
<!--- Instantiate the UserGateway object. --->
<cfset var objUserGW = createObject('component', 'com.packtApp.oop.dataAccess.UserGateway').init( datasource=objDataSource) />
<!--- Store the UserDAO in the Application scope. --->
<cfset Application.userDAO = objUserDAO />
<!--- Store the UserGW in the Application scope. --->
<cfset Application.userGW = objUserGW />
<!--- Instantiate the AddressDAO object. --->
<cfset var objAddressDAO = createObject('component', 'com.packtApp.oop.dataAccess.AddressDAO').init( datasource=objDataSource) />
<!--- Instantiate the AddressGateway object. --->
<cfset var objAddressGW = createObject('component', 'com.packtApp.oop.dataAccess.AddressGateway').init( datasource=objDataSource) />
<!--- Store the addressDAO in the Application scope. --->
<cfset Application.addressDAO = objAddressDAO />
<!--- Store the addressGW in the Application scope. --->
<cfset Application.addressGW = objAddressGW />
</cffunction>

Listing 7.1 - Application.cfc (before services)

Looking at the code in Listing 7.1, there are currently four objects being instantiated and added to the Application scope for persistence throughout the application.

All of these objects are directly related to the two packages we have within the code base; the User's package and the Address package respectively, and from each package we are accessing the Gateway and the Data Access Object.

Let's visualize these objects in another UML diagram, being instantiated from the Application.cfc file.

Creating a service

Although this is entirely feasible, we can and should streamline the instantiation of the objects as their dependencies dictate. This will help reduce any 'clutter' from the onApplicationStart() method, and will also help to improve the solidity and structure of our application's underlying model framework.

We will start to enhance our application's code structure even more by creating a Service Layer component for each of the packages.

Defining the User Service

The main purpose of the User Service object is to be a single point of entry for anything that requires access to user functionality, whether it's a Controller, a standard .cfm template page, or a Web Services call.

A service object is no different in terms of basic layout and structure from any of the other ColdFusion components we have written and discussed so far within this book.

Listing 7.2 - shows the bare bones structure of the CFC, complete with the init() method used for the instantiation of the object. Create a new file called UserService.cfc within the com/packtApp/oop/dataAccess directory within your application, and add the following code to the file:

<cfcomponent displayname="UserService" output="false" hint="I am the UserSVC Class used to interact with the User package.">
<!--- Pseudo-constructor --->
<cfset variables.instance = { userDAO = '',
userGW = ''
} />
<cffunction name="init" access="public" output="false" returntype="any" hint="I am the constructor method for the UserSVC Class.">
<cfargument name="datasource" required="true" type="com.packtApp.oop.beans.Datasource" hint="I am the datasource object." />
<!--- Set the initial values of the Bean --->
<cfscript>
// instantiate the Data Access Object
variables.instance.userDAO = createObject( 'component', 'UserDAO' ).init(arguments.datasource);
// instantiate the Gateway
variables.instance.userGW = createObject( 'component', 'UserGateway').init(arguments.datasource);
</cfscript>
<cfreturn this />
</cffunction>
<!--- CRUD METHODS --->
<!--- GATEWAY METHODS --->
</cfcomponent>

Listing 7.2 - UserService.cfc

The main purpose of the user service layer is to provide a simpler interface for the developer/client to interact with the detailed packages already developed. The User package needs to access, store, and retrieve information from the database, which is why the Data Access Objects and Gateway components were developed.

Although this works amazingly well, not all of the methods they contain need to be made available for 'public consumption'. For example, we don't need to expose all of the save(), exists(), or filterBy_X() methods. The packages know what methods need to be run when a certain function has been called—the next thing we can do is to simplify the way they are being called.

As we need access to the User DAO and Gateway components, we have included their instantiation within the init() method in the User Services layer, passing through the datasource object from the arguments scope as it is a required parameter in both the DAO and Gateway. We are then assigning both of the new objects to an expression within the variables.instance structure within the CFC.

The DAO and Gateway objects have been stored within the service component for use throughout the Users package. Although they are two separate objects each containing their own methods and functions, as we have merged them into the service component, all of those methods are available for us to use as though they are part of the original component.

Adding the CRUD methods

The first object instantiated in the init() method of the UserService component is the UserDAO component.

This object, as you will hopefully remember, was developed to deal with the single-row database access. The four main methods within this object dealt with the CRUD transactions; Create, Read, Update, and Delete.

We also created two other functions called save() and exists(). The save() method was written as a single entry method that would handle the creation of a new record, or the update of an existing database record by calling the exists() method to check if the ID value already existed in the database. If so, an update was performed on that row of data, if not, a new record was inserted into the database.

At this stage in development, we had already helped to streamline code and optimize the function calls by providing the ability to run one of the two database functions by only calling one method, the save() method.

Although the DAO contains six methods in total (not including the init() constructor method), we need to reference only three of them within the service layer for the users package, as it is only these three that need to be publicly accessible.

Let's add the following three functions shown in Listing 7.3 to the UserService.cfc file to add more functionality to our user package API.

<!--- CRUD METHODS --->
<cffunction name="save" access="public" output="false" hint="I save or update a User into the database.">
<cfargument name="user" required="true" type="com.packtApp.oop.beans.User" hint="I am the User bean." />
<cfreturn variables.instance.userDAO.saveUser(arguments.user) />
</cffunction>
<cffunction name="read" access="public" output="false" hint="I obtain details for a specific User from the database.">
<cfargument name="userID" required="true" type="numeric" hint="I am the ID of the user you wish to search for." />
<cfreturn variables.instance.userDAO.getUserByID(arguments.userID) />
</cffunction>
<cffunction name="delete" access="public" output="false" hint="I delete a specific User the database.">
<cfargument name="userID" required="true" type="String" hint="I am the ID of the user you wish to delete." />
<cfreturn variables.instance.userDAO.deleteUserByID(arguments.userID)/>
</cffunction>

Listing 7.3 - UserService.cfc (CRUD method callers)

Our three methods, save(), read(), and delete() have now been placed inside the service component.

All of the SQL and database transactions have already been written within the DAO itself. We do not want to duplicate any code—we have no requirement to. As the DAO has been stored within the service component, we simply need to call the relevant function within the object to run its method, variables.instance.userDao.save() for example.

Listing 7.4 - shows a clearer breakdown of the save() method from the projects service layer:

<cffunction name="save" access="public" output="false" hint="I save or update a User into the database.">
<!--- send in the required user object --->
<cfargument name="user" required="true" type="com.packtApp.oop.beans.User" hint="I am the User bean." />
<!--- call the save() method within the userDAO --->
<cfreturn variables.instance.userDAO.saveUser(arguments.user) />
</cffunction>

Listing 7.4 - breakdown of the save() method

Adding the Gateway methods

In the same way we have added the functions from the User package Data Access Object, we now need to add the functions from the Gateway object, as show in Listing 7.5.

<!--- GATEWAY METHODS --->
<cffunction name="getAllUsers" access="public" output="false" hint="I run a query of all users within the database table.">
<!--- Call the query method from the User Gateway and return the query object. --->
<cfreturn variables.instance.userGW.filterAllUsers() />
</cffunction>
<cffunction name="filterByLastName" access="public" output="false" hint="I run a query of all users within the database table based upon a required filter.">
<cfargument name="lastNameFilter" required="true" type="string" hint="I am the lastname to filter." />
<!--- Create and populate a structure object containing the filter to pass through. --->
<cfset var stuFilter = {
lastname = arguments.lastNameFilter
} />
<!--- Send the structure into the query method and return the query object. --->
<cfreturn variables.instance.userGW.filterAllUsers(stuFilter) />
</cffunction>
<cffunction name="filterByEmailAddress" access="public" output="false" hint="I query the database to find a user with a matching email address.">
<cfargument name="emailAddress" required="true" type="string" hint="I am the email address to search for." />
<!--- Create and populate a structure object containing the filter to pass through. --->
<cfset var stuFilter = {
emailAddress = arguments.emailAddress
} />
<!--- Send the structure into the query method and return the query object. --->
<cfreturn variables.instance.userGW.filterAllUsers(stuFilter) />
</cffunction>

Listing 7.5 - UserService.cfc (Gateway method callers)

Again, the methods are in place, but no SQL or database transactions are in sight. We have already written those, and have no desire to write them again. We want to keep all database calls and SQL statements locked to the DAO and Gateway objects only. This way, we only have the two files in which to change the relevant SQL if required.

The purpose of the service layer is to provide a simple interface to call the previously developed and detailed methods.

As in the previous example, once a function is called within the UserService component, the relevant method is called from the User Gateway object:

<cffunction name="getAllUsers" access="public" output="false" hint="I run a query of all users within the database table.">
<!--- Call the query method from the User Gateway and return the query object. --->
<cfreturn variables.instance.userGW.filterAllUsers() />
</cffunction>

Listing 7.6 - breakdown of the filterByParent() method

Adding an abstract class

We can further extend the functions available within the UserService object by adding a base class to the component to aid in code reuse.

We have the coreUtils.cfc component (com/packtApp/oop/utils/coreUtils.cfc), which we can use to contain generic methods and functions that we can share across all of our components if necessary. Let's add a new function to this abstract class, which will allow us to view the values of the variables.instance structure within the object we call it from:

<cfcomponent displayname="coreUtils" output="false" hint="I am the coreUtils abstract Class.">
<cffunction name="getMemento" access="public" output="false" hint="I return the variables.instance struct.">
<cfreturn variables.instance />
</cffunction>
</cfcomponent>

Listing 7.7 - com/utils/core.cfc (adding getMemento() method)

To complete this step, we next need to amend the UserService component and add the extends="" attribute to point to the coreUtils.cfc abstract class:

<cfcomponent displayname="UserSVC" output="false" hint="I am the UserSVC Class used to interact with the User package." extends="com.packtApp.oop.utils.coreUtils">

Listing 7.8 - UserService.cfc (adding the extends attribute)

In Listing 7.8, we have added the path to the coreUtils.cfc file in the extends attribute within the cfcomponent tag.

<cfdump var="#Application.userSVC.getMemento()#" />

Listing 7.9 - calling the getMemento() function

The following screenshot shows the dumped values of the code in Listing 7.9, which returns the variables.instance structure within the User Service object:

Adding an abstract class

The following UML diagram shows the UserService object and the inheritance connection with the coreUtils component.

Adding an abstract class
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.216.44.143