Building the service layer

The service layer classes and interfaces will follow the same naming conventions of our DAO layer, where Service simply replaces the Dao equivalent name:

Building the service layer

Our first definition will be for the Result class.

The Result Data Transfer Object

The service layer will communicate with the request handling tier through interfaces that return Result Data Transfer Objects (DTO). The DTO design pattern is commonly used in enterprise application programming to transfer data between different layers or subsystems. Our Result DTO will have the following three properties:

  • boolean success: This property is used if the action was successful and an appropriate data payload is available
  • String msg: This is a message that may be used by the client for logging or informational purposes
  • <T> data: This is a generically typed data payload that will be consumed by the request handling layer

The Result class is also a Value Object (VO), an immutable object whose state cannot be changed after creation. Each instance variable is marked final and we will use an appropriate ResultFactory method to create the value object instance. Value objects are a concept used in Domain-Driven Design to represent data without any conceptual identity. You can find out more about Domain-Driven Design at http://en.wikipedia.org/wiki/Domain-driven_design. The definition of the Result class follows:

package com.gieman.tttracker.vo;

import java.io.Serializable;
import java.util.List;
import java.util.Objects;

public class Result<T> implements Serializable {

    final private boolean success;
    final private T data;
    final private String msg;

    Result(boolean success, T data) {
        this.success = success;
        this.data = data;
        this.msg = null;
    }

    Result(boolean success, String msg) {
        this.success = success;
        this.data = null;
        this.msg = msg;
    }
    
    public boolean isSuccess() {
        return success;
    }

    public T getData() {
        return data;
    }

    public String getMsg() {
        return msg;
    }

    @Override
    public String toString() {

        StringBuilder sb = new StringBuilder(""Result{"");
        sb.append("success=").append(success);
        sb.append(", msg=").append(msg);

        sb.append(", data=");

        if(data == null){

            sb.append("null");

        } else if(data instanceof List){

            List castList = (List) data;
            if(castList.isEmpty()){

                sb.append("empty list");

            } else {
                Object firstItem = castList.get(0);

                sb.append("List of ").append(firstItem.getClass());
            }

        } else {
            sb.append(data.toString());
        }

        sb.append("}");

        return sb.toString();

    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 89 * hash + (this.success ? 1 : 0);
        hash = 89 * hash + Objects.hashCode(this.data);
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Result<?> other = (Result<?>) obj;
        if (this.success != other.success) {
            return false;
        }
        return Objects.deepEquals(this.data, other.data);
    }
}

You will notice that the Result constructors are package-private (cannot be created by classes outside of the package). The Result value object instantiation will be managed by the ResultFactory class:

package com.gieman.tttracker.vo;

public class ResultFactory {

    public static <T> Result<T> getSuccessResult(T data) {
        return new Result(true, data);
    }
    public static <T> Result<T> getSuccessResult(T data, String msg) {
        return new Result(true, msg);
    }

    public static <T> Result<T> getSuccessResultMsg(String msg) {
        return new Result(true, msg);
    }

    public static <T> Result<T> getFailResult(String msg) {
        return new Result(false, msg);
    }
}

The static utility methods will create and return Result instances configured for the appropriate purpose in our service layer.

In our design, a failure is considered to be a recoverable state of the application. Attempting to log in with an invalid username/password combination would be an example of a failed action. Not having permission to perform a delete would be another possible failure action. The client of the service layer can recover from such actions and present graceful messages to the user by examining the msg of the Result. An alternate design pattern for handling failures is through Java-checked exceptions; an exception is thrown when a failure is encountered. Implementing such a design pattern forces the client to catch the exception, determine the cause of the exception, and handle processing accordingly. We prefer our design for handling failures and recommend you to not use checked exceptions unless a truly exceptional situation has occurred. The resulting code is cleaner to read and we can avoid the overhead of working with exceptions.

The AbstractService.java class

All service layer implementations will extend the AbstractService class to provide common functionality. We will simply define a logger, @Autowire, the UserDao implementation, and add a convenience method for checking if a user is valid.

package com.gieman.tttracker.service;

import com.gieman.tttracker.dao.UserDao;
import com.gieman.tttracker.domain.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

public abstract class AbstractService {

    final protected Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    protected UserDao userDao;

    protected  final String USER_INVALID = "Not a valid user";
    protected  final String USER_NOT_ADMIN = "Not an admin user";

    protected boolean isValidUser(String username){

        User user = userDao.findByUsername(username);
        return user != null;
    }
}

As discussed in the previous chapter, Spring injects the container-managed bean with matching type for each of the @Autowired annotated fields. Each service layer implementation that extends the AbstractService class will hence have access to the UserDao instance.

Our service layer will implement very basic security to differentiate between normal users and administrator users. The admin_role column in the ttt_user table is used to identify if a user has administrator privileges. Enterprise applications will most likely have LDAP realms with appropriate roles configured for different user groups but the principle is the same; we need to be able to identify if a user is allowed to perform an action. The administrator role will be the only role on our 3T application and we will now add a helper method to the User class to identify whether the user is an administrator:

  public boolean isAdmin(){
    return adminRole == null ? false : adminRole.equals('Y'),
  }

The service layer implementations will use this new method to test if the user is an administrator.

..................Content has been hidden....................

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