2015年6月21日日曜日

[業務ロジック][機能単位][実装][サービス]ServiceParameter

前述したサービス入力パラメータ(ServiceInputParameter)とサービス出力パラメータ(ServiceOutputParameter)には共通する役割(ロール)があるため、スーパークラス(ServiceParameter)を切り出している。

ServiceParameter

├ServiceInputParameter
└ServiceOutputParameter

スーパークラス(ServiceParameter)の実装は、次の通り。

package service.param;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import util.PropUtil;
import util.SvFile;
import exception.ApplicationInternalException;

/**
 * サービスパラメータ
 */
public abstract class ServiceParameter {

    /** パラメータ名 */
    private String parameterName = null;

    public String geParameterName() {
        return parameterName;
    }

    public void setParameterName(String serviceName) {
        this.parameterName = serviceName;
    }

    /** パラメータユニットのリスト */
    private List<ParameterUnit> parameterUnitList = null;

    protected List<ParameterUnit> getParameterUnitList() {
        return parameterUnitList;
    }

    protected void setParameterUnitList(List<ParameterUnit> parameterUnitList) {
        this.parameterUnitList = parameterUnitList;
    }

    /** 文字列マップ */
    private Map<String, String> stringMap = null;

    /** 整数マップ */
    private Map<String, Integer> integerMap = null;

    /** 整数(long)マップ */
    private Map<String, Long> longMap = null;

    /** 浮動小数マップ */
    private Map<String, Double> doubleMap = null;

    /** 日付マップ */
    private Map<String, Date> dateMap = null;

    /** オブジェクトマップ */
    private Map<String, Object> objectMap = null;

    /**
     * コンストラクタ
     */
    public ServiceParameter(String parameterName) {

        // メンバを初期化する
        this.parameterName = parameterName;
        parameterUnitList = new ArrayList<ParameterUnit>();
        stringMap = new HashMap<String, String>();
        integerMap = new HashMap<String, Integer>();
        longMap = new HashMap<String, Long>();
        doubleMap = new HashMap<String, Double>();
        dateMap = new HashMap<String, Date>();
        objectMap = new HashMap<String, Object>();

        // パラメータユニットリストを初期化する
        initializeParameterUnitList();
    }

    /**
     * パラメータユニットリストを初期化します。
     */
    protected abstract void initializeParameterUnitList();

    /**
     * パラメータユニットを追加します。
     *
     * @param parameterUnit
     *            パラメータユニット
     */
    protected void addParameterUnit(ParameterUnit parameterUnit) {
        getParameterUnitList().add(parameterUnit);
    }

    /**
     * 文字列を設定します。
     *
     * @param key
     *            キー
     * @param value
     *            設定する文字列
     */
    public void setString(String key, String value) {
        stringMap.put(key, value);
    }

    /**
     * 整数を設定します。
     *
     * @param key
     *            キー
     * @param value
     *            設定する整数
     */
    public void setInt(String key, Integer value) {
        integerMap.put(key, value);
    }

    /**
     * 整数をint型として設定します。
     *
     * @param key
     *            キー
     * @param value
     *            設定する整数(int型)
     */
    public void setIntVal(String key, int value) {
        setInt(key, Integer.valueOf(value));
    }

    /**
     * 整数を設定します。
     *
     * @param key
     *            キー
     * @param value
     *            設定する整数
     */
    public void setLong(String key, Long value) {
        longMap.put(key, value);
    }

    /**
     * 整数をlong型として設定します。
     *
     * @param key
     *            キー
     * @param value
     *            設定する整数(long型)
     */
    public void setLongVal(String key, long value) {
        setLong(key, Long.valueOf(value));
    }

    /**
     * 浮動小数を設定します。
     *
     * @param key
     *            キー
     * @param value
     *            設定する浮動小数点
     */
    public void setDouble(String key, Double value) {
        doubleMap.put(key, value);
    }

    /**
     * 浮動小数をdouble型として設定します。
     *
     * @param key
     *            キー
     * @param value
     *            設定する浮動小数点(double型)
     */
    public void setDoubleVal(String key, double value) {
        setDouble(key, Double.valueOf(value));
    }

    /**
     * 日付を設定します。
     *
     * @param key
     *            キー
     * @param value
     *            設定する日付
     */
    public void setDate(String key, Date value) {
        dateMap.put(key, value);
    }

    /**
     * 日付をlong型として設定します。
     *
     * @param key
     *            キー
     * @param value
     *            設定する日付(long型)
     */
    public void setDateVal(String key, long value) {
        setDate(key, new Date(value));
    }

    /**
     * オブジェクトを設定します。
     *
     * @param key
     *            キー
     * @param value
     *            設定するオブジェクト
     */
    public void setObject(String key, Object value) {
        objectMap.put(key, value);
    }

    /**
     * 文字列を取得します。
     *
     * @param key
     *            キー
     * @return 文字列
     */
    public String getString(String key) {
        return stringMap.get(key);
    }

    /**
     * 整数を取得します。
     *
     * @param key
     *            キー
     * @return 整数
     */
    public Integer getInt(String key) {
        return integerMap.get(key);
    }

    /**
     * 整数をint型として取得します。
     *
     * @param key
     *            キー
     * @return 整数(int型)
     */
    public int getIntVal(String key) {
        return getInt(key).intValue();
    }

    /**
     * 整数を取得します。
     *
     * @param key
     *            キー
     * @return 整数
     */
    public Long getLong(String key) {
        return longMap.get(key);
    }

    /**
     * 整数をlong型として取得します。
     *
     * @param key
     *            キー
     * @return 整数(long型)
     */
    public int getLongVal(String key) {
        return getLong(key).intValue();
    }

    /**
     * 浮動小数を取得します。
     *
     * @param key
     *            キー
     * @return 浮動小数
     */
    public Double getDouble(String key) {
        return doubleMap.get(key);
    }

    /**
     * 浮動小数をdouble型として取得します。
     *
     * @param key
     *            キー
     * @return 浮動小数(double型)
     */
    public double getDoubleVal(String key) {
        return getDouble(key).doubleValue();
    }

    /**
     * 日付を取得します。
     *
     * @param key
     *            キー
     * @return 日付
     */
    public Date getDate(String key) {
        return dateMap.get(key);
    }

    /**
     * 日付をlong型として取得します。
     *
     * @param key
     *            キー
     * @return 日付(long型)
     */
    public long getDateVal(String key) {
        return getDate(key).getTime();
    }

    /**
     * オブジェクトを取得します。
     *
     * @param key
     *            キー
     * @return オブジェクト
     */
    public Object getObject(String key) {
        return objectMap.get(key);
    }

    //
    // 以下、CSVファイル出力は当初出力パラメータのみとしていたが、
    // 入力パラメータもCSVとして残すことでバッチ実行による再現試験を
    // 容易にするため、本クラスにメソッドを移動した。
    //

    /**
     * CSVファイルパスで示されるCSVファイルを開き、パラメータユニットの設定に従ってパラメータを書き込みます。
     * CSVファイルパスのみを指定した場合、CSVファイルの最初の1行はヘッダ行として読み飛ばされます。
     *
     * @param filePath
     *            CSVファイルパス
     */
    public void exportCsvFile(String filePath) {

        // デフォルトでは最初の1行をヘッダ行とみなす
        exportCsvFile(filePath, true);
    }

    /**
     * CSVファイルパスで示されるCSVファイルを開き、パラメータユニットの設定に従ってパラメータを書き込みます。
     *
     * @param filePath
     *            CSVファイルパス
     * @param skipFirstLine
     *            最初の行を読み飛ばす場合はtrue、そうでなければfalse
     */
    public void exportCsvFile(String filePath, boolean skipFirstLine) {

        // パラメータユニット設定に従い、ヘッダ行のデータを作成する
        List<List<String>> svLineList = new ArrayList<List<String>>();
        svLineList.add(getSvFileHeader());

        // パラメータユニット設定に従い、全てのパラメータをSV行データに変換する
        getSvLineList(svLineList);

        // 作成したSV行でファイルを作成し、セーブする
        save(filePath, ",", svLineList);
    }

    /**
     * TSVファイルパスで示されるCSVファイルを開き、パラメータユニットの 設定に従ってサービス入力パラメータを書き込みます。
     * TSVファイルパスのみを指定した場合、TSVファイルの最初の1行はヘッダ行として読み飛ばされます。
     *
     * @param filePath
     *            TSVファイルパス
     */
    public void exportTsvFile(String filePath) {

        // デフォルトでは最初の1行をヘッダ行とみなす
        exportTsvFile(filePath, true);
    }

    /**
     * TSVファイルパスで示されるCSVファイルを開き、パラメータユニットの設定に従ってサービス入力パラメータを書き込みます。
     *
     * @param filePath
     *            TSVファイルパス
     * @param skipFirstLine
     *            最初の行を読み飛ばす場合はtrue、そうでなければfalse
     */
    public void exportTsvFile(String filePath, boolean skipFirstLine) {

        // パラメータユニット設定に従い、ヘッダ行のデータを作成する
        List<List<String>> svLineList = new ArrayList<List<String>>();
        svLineList.add(getSvFileHeader());

        // パラメータユニット設定に従い、全ての出力パラメータをSV行データに変換する
        getSvLineList(svLineList);

        // 作成したSV行でファイルを作成し、セーブする
        save(filePath, "\t", svLineList);
    }

    /**
     * パラメータユニット設定に従い、SVファイルのヘッダを作成します。
     *
     * @return SVファイルのヘッダ行
     */
    public List<String> getSvFileHeader() {

        // パラメータユニット設定に従い、SVファイルのヘッダを作成する
        List<String> svLine = new ArrayList<String>();
        for (int i = 0; i < getParameterUnitList().size(); i++) {

            // パラメータユニットの列とSVカラムの並びは同じであると仮定する
            ParameterUnit parameterUnit = getParameterUnitList().get(i);

            // 型に応じた入力値設定を行う
            String headerElement = parameterUnit.getLogicalName() + "( "
                    + parameterUnit.getPhysicalName() + " : "
                    + parameterUnit.getType() + " )";
            svLine.add(headerElement);
        }

        // 作成したSV行データを呼び出し側に戻す
        return svLine;
    }

    /**
     * SVファイル(CSV or TSV)に追加する出力パラメータ、全行のデータを取得します。
     *
     * @param svLineList
     */
    protected void getSvLineList(List<List<String>> svLineList) {

        // 事前に設定されているデータに基づき、SV行を作成する
        svLineList.add(getSvLine());
    }

    /**
     * SVファイル(CSV or TSV)に追加する出力パラメータ行データを取得します。
     */
    public List<String> getSvLine() {

        // パラメータユニット設定に従い、全ての入力パラメータをTSVファイルから取得する
        List<String> svLine = new ArrayList<String>();
        for (int i = 0; i < getParameterUnitList().size(); i++) {

            // パラメータユニットの列とSVカラムの並びは同じであると仮定する
            ParameterUnit parameterUnit = getParameterUnitList().get(i);

            // 型に応じた入力値設定を行う
            if ("String".equalsIgnoreCase(parameterUnit.getType())) {
                svLine.add(getString(parameterUnit.getPhysicalName()));
            }
            if ("int".equalsIgnoreCase(parameterUnit.getType())
                    || "Integer".equalsIgnoreCase(parameterUnit.getType())) {
                if (getInt(parameterUnit.getPhysicalName()) == null) {
                    svLine.add("");
                } else {
                    svLine.add(getInt(parameterUnit.getPhysicalName())
                            .toString());
                }
            }
            if ("long".equalsIgnoreCase(parameterUnit.getType())
                    || "Long".equalsIgnoreCase(parameterUnit.getType())) {
                if (getLong(parameterUnit.getPhysicalName()) == null) {
                    svLine.add("");
                } else {
                    svLine.add(getLong(parameterUnit.getPhysicalName())
                            .toString());
                }
            }
            if ("Double".equalsIgnoreCase(parameterUnit.getType())) {
                if (getDouble(parameterUnit.getPhysicalName()) == null) {
                    svLine.add("");
                } else {
                    svLine.add(getDouble(parameterUnit.getPhysicalName())
                            .toString());
                }
            }
            if ("Date".equalsIgnoreCase(parameterUnit.getType())) {
                if (getDate(parameterUnit.getPhysicalName()) == null) {
                    svLine.add("");
                } else {
                    svLine.add(getDate(parameterUnit.getPhysicalName())
                            .toString());
                }
            }
        }

        // 作成したSV行データを呼び出し側に戻す
        return svLine;
    }

    /**
     * 引数で 指定されたファイルパス、デリミタ、SV行データリストでSVファイルを作成し、保存します。
     * SVファイルクラスの同名メソッドの単純ラッピングですが、例外をアプリケーション内部例外とし、 スローステートメントを書かなくてよい点が異なります。
     *
     * @param filePath
     *            ファイルパス
     * @param delm
     *            デリミタ
     * @param svLineList
     *            SV行データリスト
     */
    public void save(String filePath, String delm, List<List<String>> svLineList) {

        try {
            // 作成したSV行でファイルを作成し、セーブする
            SvFile svFile = new SvFile();
            svFile.save(filePath, delm, svLineList);

        } catch (IOException e) {
            throw new ApplicationInternalException(e);
        }
    }

    /**
     * CSVファイルパスを作成します。
     *
     * @return CSVファイルパス
     */
    public String createCsvFilePath() {

        // ログ記録の基本パスを取得する
        String logBasePath = PropUtil.get("serviceLog.basePath");

        // 現在日時(yyyy_MM_dd_HH_mm_ss)とサービス名を組み合わせ、CSVファイルパスを作成する
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss");
        return logBasePath + "/" + sdf.format(new Date()) + "_"
                + geParameterName() + ".csv";
    }
}

 ここで主に規定しているのは、サービスパラメータクラスをログに記録するための仕組みである。業務ロジック(サービス)は、入力に対する期待した出力が得られることを以って「正常」と判断できるため、自動テストを実装する場合は入力パラメータと出力パラメータをログに記録し、テキスト比較で妥当性を自動で検証する仕組みが不可欠である。
 そのため業務ロジックに渡すパラメータは入力/出力を問わず、ログ記録の仕組みが必要である。また、サービスパラメータをわざわざ抽象クラスを使ってまで抽象化する理由は、バッチ実行の仕組みを考慮してのことである。サービスをバッチから呼び出すにあたってバッチロジックを毎度実装するのはメンテナンス性の面からしても効率が悪い。入力データも出力データもログに記録できているのだから、サービス名さえ分かればバッチファイルだけでバッチ処理を実行できる仕組みが望ましい。そこでサービスのパラメータを個別のクラスとしてもよいが、もう一歩踏み込んで抽象化された共通のインターフェースとした方がよいと判断し、このようなロジック構成となった。

0 件のコメント:

コメントを投稿