今回はリソースの実装である。具体的には次のような非常にシンプルなAPIを定義することになる。
<API例(entityはリソースの1インスタンス)>
entity = create(entity);
entity = retrieve(entity);
entity = update(entity);
entity = delete(entity);
サブシステム内であれば上記APIをそのまま使う。サブシステム外ではHTTPのPOST/GET/PUT/DELETEがそれぞれ対応する。例えば、GETを実行するとシリアライズされたentityが返ってくる。以下が実行結果である。リソース実装では、このような結果を取得できることを目標とする。<参考>リソース志向
<JSON>
<XML>
さて、ここでちょっと設計について補足しておきたい。この記事では以下のように説明した。
アプリケーションの作り方であるが、具体的には次の順番で始める。この4番目まで作ったら、インスタンスを各レイヤに渡して後は個別での作業となる。(これについては次回以降説明する)
1.画面のモックアップを作る
2.モデリングを行い画面のEntity(Model)を作成する
3.テーブルのEntityを作成する(DAO)
4.Entityのインスタンスを作成する
(※)Reflex設計ではModelをEntityの構造として定義してドメインとしてシステム全体で使用する
ここでいうモデリングとは、実は分析クラスを設計することを意味している。つまり、2.の画面のEntity(Model)を作成というのは、Boundaryの分析クラスを設計することと同じ意味である。通常は、Controlの分析クラス、Entityの分析クラスとそれぞれ設計していくのだが、Boundaryの分析クラスからEntityの分析クラスを導きだして、それを共通のドメインとして使おうというのが、Reflex設計における重要なポイントとなる。
全体で行う設計は、Entityの分析クラスの構造を定義し、そのインスタンスを各レイヤに配るところまででおしまい。そのため、全体の設計で登場するのはModelではなくEnitityとなる。
Modelは設計クラスであり分析クラスから導出するものだ。ViewやContolerも同様である。しかし、MVCモデルは進化するでも述べたが、今回は設計クラスのモデリングは行わない。
テーブルの構造は、BoundaryとContorolでは関知しないので、3.テーブルのEntityを作成する(DAO)は、正しくはResourceレイヤの責務のなかで行う。またDAOは設計クラスなので、分析クラスのモデリングでは登場しない。
<MVCモデルは進化するの抜粋>
そこで、得た結論は、
否定の6.オブジェクト設計をしない
である。正確には、設計クラスのModelをオブジェクトにはしないで、オブジェクトで扱うデータに関心を集めようというもの。Modelとは、モデリングによってできた成果物(※)でありオブジェクトであるから、データとプログラムがくっついている状態。それからプログラムを切り離してデータのみに関心を集めて考える。データだけであればオブジェクトではないのでオブジェクト設計をしないことを意味する。とはいえ、分析クラスまでは設計して論理ビューを作る。論理ビューで定義したサブシステム内であれば、勝手に設計/実装して構わないという感じになる。
(※ Modelの定義は曖昧で様々な説明が存在する。私はこの説明が一番好きだ。)
Reflex設計では、設計クラスのモデリングは行わないのでModelという言葉はなじまない。
一方、分析クラスで導出したEntityクラスは、システム全体でドメインとして使用するため、画面でもEntityという言葉が頻発することになる。画面でEntityはちょっと違和感があるので、Modelと呼ぶ方が自然かもしれないが、ここは敢えてEntityと呼ぶことで、システム全体のドメインであることを強調したい。各レイヤにおいては、勝手に設計クラスを作成しても構わない。
次に、本題であるリソースの実装について説明する。ここからは各レイヤ内の閉じた話なので、設計クラスのModelという言葉が登場する。具体的には以下のように実装する。
1.EntityにControllerを実装してResource Modelを実装する
2.DAO Modelを実装する
3.DAO ModelとResource Modelの変換をBlogicで行う
1.EntityにControllerを実装してResource Modelを実装する
Reflex表現から自動生成したBeanは、項目とアクセサー(getter/setter)だけを実装したものであり、データを格納する器の機能しかもたない。Reflexでは、以下のように、ControllerであるReportCtlを継承することにより、CRUD操作が可能なModelとして扱うことができるようになっている。CRUD操作はそれぞれHTTPのPOST/GET/UPDATE/DELETEに対応する。また、トランザクション管理を行っており、要素の入れ子にも対応している。つまり、子要素でエラーとなった場合に、Exceptionをスローすることで、一番親のところ(Servlet)でRollback機能が働く仕組みが提供されている。コードに中に明示的なcommit/rollbackの記述は必要ない。
<Report model>
package jp.reflexworks.pms.model;
import jp.reflexworks.pms.controller.ReportCtl;
import java.util.List;
public class Report extends ReportCtl {
public List
public List
public List
public List
public String userid;
public String month;
public String responsecode;
public String createddate;
public List
return this.activity;
}
public void setActivity(List
this.activity = activity;
}
public List
return this.assessment;
}
・・・・
}
2.DAO Modelの実装
以下は、実際のDAO ModelであるActivityDetailと、テーブルにアクセスするSQLを記述した部分である。ちなみに、ActivityDetailは、テーブルの構造をReflex表現で定義して自動生成したクラスである。先ほどは画面から項目を整理してEntityを自動生成したが、今度はテーブルの項目からReflex表現を作ることで、DAO Modelを自動生成する。
また、DAOでは、Jakarta CommonsのDBUtilを使って直接SQLを書くようにしており、敢えてO/Rマッパは使っていない。一見、O/Rマッパは便利そうに思えるが、逆にブラックボックスが仇となって痛い目に見ることもあるため、展開された内部のSQLは把握しておく必要がある。結局、SQLを避けて通れないのであれば、直接記述する方法が最も理にかなっていると考えた。ここは、基本に立ち返ってSQLを記述するのがベストである。とはいえ、SQLを直接記述しても、DBUtilであれば項目名とフィールド名を一致させておくことで、ResultSetからわざわざ詰め替えなくてもO/Rマッパの検索結果のように結果オブジェクトを得ることができる。これで十分である。
<DAO Model>
package jp.reflexworks.pms.model;
import jp.reflexworks.pms.controller.ActivitydetailCtl;
public class Activitydetail extends ActivitydetailCtl {
public String userid;
public String actiondate;
public String timefrom;
public String timeto;
public String taskid;
public String content;
public String percent;
public String getUserid() {
return this.userid;
}
public void setUserid(String userid) {
this.userid = userid;
}
・・・
}
<DAO>
import java.sql.SQLException;
import java.util.List;
import org.apache.commons.dbutils.ResultSetHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import jp.reflexworks.pms.model.Activitydetail;
import jp.sourceforge.reflex.dao.DBAccess;
public class ActivitydetailDao extends DBAccess {
private static final String SELECT_LIST = "select" +
" userid" +
",actiondate" +
",timefrom" +
",timeto" +
",taskid" +
",content" +
",percent" +
" from activitydetail" +
" where userid = ?" +
" and substr(actiondate, 1, 6) = ?" +
" order by actiondate";
private static final String INSERT = "insert into activitydetail (" +
" userid" +
",actiondate" +
",timefrom" +
",timeto" +
",taskid" +
",content" +
",percent" +
" ) values (?,?,?,?,?,?,?)";
public List
ResultSetHandler rsh = new BeanListHandler(Activitydetail.class);
String[] params = new String[2];
params[0] = userid;
params[1] = month;
return (List
}
public int create(Activitydetail activitydetail) throws SQLException {
String[] params = new String[7];
params[0] = activitydetail.getUserid();
params[1] = activitydetail.getActiondate();
params[2] = activitydetail.getTimefrom();
params[3] = activitydetail.getTimeto();
params[4] = activitydetail.getTaskid();
params[5] = activitydetail.getContent();
params[6] = activitydetail.getPercent();
return update(INSERT, params);
}
・・・
3.DAO ModelとResource Modelの変換をBlogicで行う
Blogicの役割はEntityの変換である。単なる変換もあれば計算などの処理が含まれる場合もある。(以下の定義を参照)
なぜこうしているかについては、「モデルの設計」に詳しく書いている。
1.entity = blogic(entity)
2.Blogicは揮発性である(ステートレスでありデータを保持しない。また、外部リソースへのI/Oはない)
3.Model内からのみ使用できるメソッドである(private)
以下はReportCtlのソースである。ここで実際に行っているのは、DAO ModelとResource Modelの変換である。
ReportCtlは、ReflexフレームワークのResourceOperatorを継承しており、onRetrieve()をoverrideしている。onRetrieve()では、DAO Modelであるactivitydetails, activitysummariesを使ってDBから検索し、ReportBlogicによりResource ModelであるReportに変換している。
package jp.reflexworks.pms.controller;
import jp.reflexworks.pms.blogic.ReportBlogic;
import jp.reflexworks.pms.model.Activitydetail;
import jp.reflexworks.pms.model.ActivitydetailList;
import jp.reflexworks.pms.model.Activitysummary;
import jp.reflexworks.pms.model.ActivitysummaryList;
import jp.reflexworks.pms.model.Report;
import jp.reflexworks.pms.model.Taskinfo;
import jp.sourceforge.reflex.controller.ResourceOperator;
public class ReportCtl extends ResourceOperator {
private ResourceOperator activitydetail;
private ResourceOperator activitysummary;
private ResourceOperator taskinfo;
private ReportBlogic blogic = new ReportBlogic();
/*
* 初期処理
*/
public void init() throws Exception {
activitydetail = factory.get(Activitydetail.class);
activitysummary = factory.get(Activitysummary.class);
taskinfo = factory.get(Taskinfo.class); }
/*
* 検索処理
*/
public Object onRetrieve() throws Exception {
// ActivitydetailのonRetrieve呼び出し
ActivitydetailList activitydetails = (ActivitydetailList)activitydetail.onRetrieve();
// ActivitysummaryのonRetrieve呼び出し
ActivitysummaryList activitysummaries = (ActivitysummaryList)activitysummary.onRetrieve();
// Reportクラスに変換
Report report = blogic.convert(activitydetails, activitysummaries);
return report;
}
・・・
登録におけるonCreate()ではReportBlogic()を使って、入力であるReportオブジェクトの中のacivityからactivitydetaisに変換を行っている。(以下の太字)
package jp.reflexworks.pms.controller;
import java.util.List;
import java.util.Date;
import jp.reflexworks.pms.blogic.ReportBlogic;
import jp.reflexworks.pms.dao.ActivitydetailDao;
import jp.reflexworks.pms.model.Activitydetail;
import jp.reflexworks.pms.model.ActivitydetailList;
import jp.reflexworks.pms.model.Report;
import jp.sourceforge.reflex.controller.ResourceOperator;
import jp.sourceforge.reflex.util.HttpStatus;
import jp.sourceforge.reflex.exception.InvokeException;
public class ActivitydetailCtl extends ResourceOperator {
private ReportBlogic blogic = new ReportBlogic();
・・・
/*
* 登録処理
*/
public Object onCreate() throws Exception {
ActivitydetailDao dao = (ActivitydetailDao)daoFactory.create(ActivitydetailDao.class);
Object entities = this.getEntities();
ActivitydetailList activitydetais = null;
int cnt = 0;
if (entities instanceof Report) {
Report report = (Report)entities;
if (report != null) {
activitydetais = blogic.getActivitydetailList(report);
}
} else if (entities instanceof ActivitydetailList) {
activitydetais = (ActivitydetailList)entities;
}
if (activitydetais != null) {
List
if (activitydetailList != null) {
for (int i = 0; i < activitydetailList.size(); i++) {
Activitydetail activitydetail = activitydetailList.get(i);
cnt += dao.create(activitydetail);
}
}
} else if (entities instanceof Activitydetail) {
Activitydetail activitydetail = (Activitydetail)entities;
cnt += dao.create(activitydetail);
} else {
throw new InvokeException(HttpStatus.SC_NOT_ACCEPTABLE);
}
Date now = new Date();
Report ret = new Report();
ret.setResponsecode(HttpStatus.SC_CREATED);
ret.setCreateddate(now.toString());
return ret;
}
・・
blogicのソースは以下のとおり。
package jp.reflexworks.pms.blogic;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Calendar;
import java.util.Date;
import java.text.SimpleDateFormat;
import jp.reflexworks.pms.model.Activity;
import jp.reflexworks.pms.model.Activitydetail;
import jp.reflexworks.pms.model.ActivitydetailList;
import jp.reflexworks.pms.model.Activitysummary;
import jp.reflexworks.pms.model.ActivitysummaryList;
import jp.reflexworks.pms.model.Assessment;
import jp.reflexworks.pms.model.Plan;
import jp.reflexworks.pms.model.Report;
import jp.reflexworks.pms.model.Task;
import jp.reflexworks.pms.model.Taskinfo;
import jp.reflexworks.pms.model.TaskinfoList;
public class ReportBlogic {
public Report convert(ActivitydetailList activitydetails, ActivitysummaryList activitysummaries) {
Report report = new Report();
Date now = new Date();
SimpleDateFormat format = new SimpleDateFormat("yyyy/MM/dd");
String createddate = format.format(now);
List
List
List
List
// activity
if (activitydetails != null && activitydetails.getActivitydetailList() != null) {
for (int i = 0; i < activitydetails.getActivitydetailList().size(); i++) {
Activitydetail activitydetail = activitydetails.getActivitydetailList().get(i);
Activity activity0 = new Activity();
Activity activity1 = new Activity();
Activity activity2 = new Activity();
Activity activity3 = new Activity();
Activity activity4 = new Activity();
Activity activity5 = new Activity();
activity0._$$text = editMD(activitydetail.getActiondate());
activity1._$$text = editHM(activitydetail.getTimefrom());
activity2._$$text = editHM(activitydetail.getTimeto());
activity3._$$text = activitydetail.getTaskid();
activity4._$$text = activitydetail.getContent();
activity5._$$text = editPercent(activitydetail.getPercent());
activityList.add(activity0);
activityList.add(activity1);
activityList.add(activity2);
activityList.add(activity3);
activityList.add(activity4);
activityList.add(activity5);
}
}
if (activitysummaries != null && activitysummaries.getActivitysummaryList() != null) {
String breakTaskid = "";
for (int i = 0; i < activitysummaries.getActivitysummaryList().size(); i++) {
Activitysummary activitysummary = activitysummaries.getActivitysummaryList().get(i);
// assessment
Assessment accessment = new Assessment();
accessment._$$text = activitysummary.getAssessment();
assessmentList.add(accessment);
// plan
Plan plan = new Plan();
plan._$$text = activitysummary.getPlan();
planList.add(plan);
// task
if (!breakTaskid.equals(activitysummary.getTaskid())) {
Task task0 = new Task();
Task task1 = new Task();
Task task2 = new Task();
Task task3 = new Task();
Task task4 = new Task();
Task task5 = new Task();
task0._$$text = activitysummary.getTaskid();
task1._$$text = activitysummary.getDescription();
task2._$$text = activitysummary.getOutput();
task3._$$text = editMD(activitysummary.getTaskfrom());
task4._$$text = editMD(activitysummary.getTaskto());
task5._$$text = activitysummary.getMm();
taskList.add(task0);
taskList.add(task1);
taskList.add(task2);
taskList.add(task3);
taskList.add(task4);
taskList.add(task5);
breakTaskid = new String(activitysummary.getTaskid());
}
}
}
report.setActivity(activityList);
report.setAssessment(assessmentList);
report.setPlan(planList);
report.setTask(taskList);
report.setCreateddate(createddate);
return report;
}
public ActivitydetailList getActivitydetailList(Report report) {
if (report == null) {
return null;
}
ActivitydetailList activitydetailList = null;
List
if (activityList != null) {
activitydetailList = new ActivitydetailList();
List
Iterator
while (it.hasNext()) {
Activitydetail activitydetail = new Activitydetail();
activitydetail.setUserid(report.getUserid()); // userid
if (it.hasNext()) {
Activity activity0 = it.next();
activitydetail.setActiondate(editDbYMD(report.getMonth(), activity0._$$text)); // actiondate
}
if (it.hasNext()) {
Activity activity1 = it.next();
activitydetail.setTimefrom(editDbHMD(activity1._$$text)); // timefrom
}
if (it.hasNext()) {
Activity activity2 = it.next();
activitydetail.setTimeto(editDbHMD(activity2._$$text)); // timeto
}
if (it.hasNext()) {
Activity activity3 = it.next();
activitydetail.setTaskid(activity3._$$text); // taskid
}
if (it.hasNext()) {
Activity activity4 = it.next();
activitydetail.setContent(activity4._$$text); // content
}
if (it.hasNext()) {
Activity activity5 = it.next();
activitydetail.setPercent(trimPercent(activity5._$$text)); // percent
}
activitydetails.add(activitydetail);
}
activitydetailList.setActivitydetailList(activitydetails);
}
return activitydetailList;
}
・・・
0 件のコメント:
コメントを投稿