ユースケース駆動開発実践_実装

ユースケース駆動開発実践_実装

これまで進めてきたモデリングで実装をしてみました。ソースコードこちらになります。
Javaで実装しDBアクセスのライブラリとしてMyBatisを使用しています。 とりあえず、以下のユースケースに対応しています。

  • ログイン
  • 商品検索
  • カートの商品更新
  • カートの内容確認
  • 注文確定

それぞれ以下の様にリクエストを投げれる様にしています。

ログイン

curl -i --cookie-jar cookie http://localhost:8080/preflight
TOKEN=$( cat cookie | grep 'XSRF' | cut -f7 )
SESSION=`curl -i --cookie cookie -d "_csrf=$TOKEN" -d name=user1 -d password=user1 -L http://localhost:8080/login | grep -E "SESSION\=[^ ]+" -o`
または
SESSION=`curl -i --cookie cookie  -H "X-XSRF-TOKEN:$TOKEN" -d name=user1 -d password=user1 -L http://localhost:8080/login | grep -E "SESSION\=[^ ]+" -o`

商品検索

curl -i -XGET --cookie cookie -b "$SESSION" -d "_csrf=$TOKEN" http://localhost:8080/product/search?limit=20&offset=0

カートの商品更新

curl -i -XPOST --cookie cookie -b "$SESSION" -H "X-XSRF-TOKEN:$TOKEN" -H 'Content-Type:application/json' -d '{"itemId": 1, "number": 2 }' http://localhost:8080/order/updateItem

カートの内容確認

curl -i -XGET --cookie cookie -b "$SESSION" -H "X-XSRF-TOKEN:$TOKEN" http://localhost:8080/order/curt_info

購入確定

curl -i -XPOST --cookie cookie -b "$SESSION" -H "X-XSRF-TOKEN:$TOKEN" -H 'Content-Type:application/json' -d '{"paymentType": 1 }' http://localhost:8080/order/confirm

集約

前回までのモデリングで集約のルートとなっていたのは注文クラスとなっていましたが、 対象のクラスは jp.co.teruuu.ec_modeling.app.domain.order.OrderEntity になっています。OrderEntityでは注文周りの操作に関するメソッドを提供しています。具体的にはカート内の商品検索や注文確定のを提供していて、集約内のデータの整合性を取れる様にしています。 商品更新のメソッドは以下の様にしています。

  public OrderEntity updateItem(ProductId productId, int price, int number) throws OrderException{
    if(!order.canItemUpdate()){
      throw new ItemUpdateException("cant item update");
    }
    Optional<Item> kizon = this.items.stream()
            .filter(i->i.getProductId().equals(productId)).findAny();
    if(kizon.isPresent()){
      Item item = kizon.get();
      item.setPrice(price);
      item.setCurrentPrice(price);
      item.setNumber(number);
      item.setUpdateDate(LocalDateTime.now());
    } else {
      this.items.add(new Item(productId, price, price, number, LocalDateTime.now()));
    }
    return this;
  }

この関数内では order.canItemUpdate() で品目更新が可能かどうかをチェックし、もし品目更新が不可能な状態でこの関数が呼ばれたらエラーを投げる様にしています。また品目更新が可能かどうかは Orderクラスのメソッドを呼び出しているのですが、これは Orderクラスの属性としてOrderStatusを持っており品目の更新が可能かどうかを管理する責務をここに与えているからです。集約のルートは特にそうですがクラス内の属性を決めれば、そのクラスの責務と外から呼び出せるメソッドが決まると思います。クラス内の属性を操作をそのクラスに限定することでクラス感は疎結合で、データの整合性を取りやすくなります。

リポジトリ

データの登録や更新、削除をするときリポジトリからデータを取り出せる様にしているのですが、集約のオブジェクトをするときは集約全体のオブジェクトをリポジトリから取り出す様にしています。注文の集約のリポジトリとしてjp.co.teruuu.ec_modeling.app.domain.order.repository.OrderRepositoryを使っていまして、現在のカートを取り出すときは以下の関数を使っています。

  public OrderEntity getCurt(UserId userId){
    Order order = orderDao.findByUserIdStatus(userId.getId(), OrderStatus.Shopping.getCode());
    if(order == null){
      return new OrderEntity(userId.getId());
    } else {
     return makeOrderEntity(order);
    }
  }

  private OrderEntity makeOrderEntity(Order order){
    OrderId orderId = order.getOrderId();
    List<Item> items = itemDao.findByOrderId(orderId);
    items.forEach(i-> {
      i.setCurrentPrice(productDao.findById(i.getProductId().getId()).getPrice());
    });
    PaymentInfo paymentInfo = paymentInfoDao.findByOrderId(orderId.getId());
    List<UsedCoupon> usedCoupons = usedCouponDao.findByOrderId(orderId.getId());
    return new OrderEntity(order, items, Optional.ofNullable(paymentInfo), usedCoupons);
  }

ユーザの現在のカートはorderテーブルからuserIdとOrderStatusを指定して取り出せる様になっているのですが、存在しない場合はOrderEntityを新規で作成するためのコンストラクタを呼び出しています。テーブル内でデータをどう持っているかはリポジトリ内で考慮する様にしており、集約内ではテーブル内でデータをどう保持しているかとかは気にしないでも良い様にしています。

登録や更新、削除の操作はこの様に集約のオブジェクトを取り出す様にしているのですが、検索をするときにリポジトリごとで対象の集約を制限すると面倒だったりします。例えばカート内容確認で現在の品目と品目に紐づく商品の情報を返したい場合、品目詳細のリポジトリから取り出せる様にしています。そのためのdtoとしてjp.co.teruuu.ec_modeling.app.app_service.dto.ItemDetailを以下の様に用意しています。

public class ItemDetail {
  int itemId;
  String name;
  int price;
  int number;
  int currentPrice;
  String description;
  LocalDateTime updateDate;
  ,,,
}

ここでは注文と商品の集約を横断してデータを取り出しています。

アプリケーションサービス

アプリケーションサービスはリポジトリから生成したドメインオブジェクトを操作したり、検索結果を返したりします。ここではトランザクションの管理もここでやっています。

@Service
public class OrderService {
  @Autowired
  OrderRepository orderRepository;

  @Autowired
  ProductRepository productRepository;

  @Autowired
  ItemDetailRepository itemDetailRepository;

  public boolean updateItem(LoginUser user, int productId, int number){
    return updateItemFunc(user, productId, number);
  }

  public boolean confirm(LoginUser user, PaymentType paymentType){
    return confirmFunc(user, paymentType);
  }

  public List<ItemDetail> curtInfo(LoginUser user){
    return curtInfoFunc(user);
  }

  @Transactional(readOnly = false)
  private boolean updateItemFunc(LoginUser user, int productId, int number){
    OrderEntity orderEntity = orderRepository.getCurt(user.getUserId());
    Optional<Product> product = productRepository.findByProductId(productId);

    if(product.isPresent()){
      try{
        orderEntity.updateItem(product.get().getProductId(), product.get().getPrice(), number);
        orderRepository.updateItem(orderEntity, product.get().getProductId());
        return true;
      } catch(OrderException e) {
        return false;
      }
    } else {
      return false;
    }
  }

  @Transactional(readOnly = false)
  private boolean confirmFunc(LoginUser user, PaymentType paymentType){
    OrderEntity orderEntity = orderRepository.getCurt(user.getUserId());
    try {
      orderEntity.confirm(paymentType);
      orderRepository.save(orderEntity);
    } catch(OrderException e){
      return false;
    }
    return true;
  }

  @Transactional(readOnly = true)
  private List<ItemDetail> curtInfoFunc(LoginUser user){
    OrderEntity orderEntity = orderRepository.getCurt(user.getUserId());
    return itemDetailRepository.findByOrderId(orderEntity.getOrder().getOrderId());
  }
}

ユースケース駆動開発実践_詳細設計

ユースケース駆動開発実践ガイドに従うとユースケースモデリング(ユースケース図作成、ドメインモデリング)、概念設計(ロバストネス図の作成、ドメインモデルの更新)と進んだら次は詳細設計(シーケンス図作成、クラス図再生)になります。
前回概念設計まで進んだので、今回は詳細設計に入りたいと思います。ただしシーケンス図は実装に近いのでクラス図の作成のみにしたいと思います。

ドメインモデルを見直す

とりあえず現在のドメインモデルを見直してみます。 f:id:steavevaivai:20190120102452p:plain

これは機能要求の名詞毎の関係性を図示化しただけなので、このまま実装するのにはいくつか不都合が発生します。
それでは、現時点でのドメインモデルで実装する場合に発生しそうな問題点をまとめてみたいと思います。

  • ショッピングカートと注文が似ている
    • 共に品目を集約していますが、購入前のものがショッピングカートに紐付き購入後のものは注文に紐づく様になっている
    • このまま実装しようとした場合、購入時にショッピングカートに紐づく品目を注文に紐づく様に更新が必要になる

とりあえずこの要件定義の内容で特に問題になりそうなのはここな気がします。 それから現在のドメインモデルではクラスの責務がわかりづらいと思うので、クラスが担当すべきだと思う責務もまとめてみたいと思います。

  • クラスの担当すべき責務が明確になっていない
    • 商品の購入状況はどこで管理するか
    • 注文の支払い状況はどこで管理するか
    • カートに入れた商品の金額が変わったことをどう確認するか

本当はもっと問題が出てくると思うのですが、その時点で思いついているものをもとにドメインモデルを更新していきたいと思います。

ショッピングカートと注文の集約について

トランザクションの管理が絡むのですが、集約間のトランザクションをマージするのはデットロックなどが発生する原因なのでバッドケースとされています。そうならないためにも集約間で順番に更新をしたり結果的に整合性が取れていたらワーカスレッドの別の集約の更新を任すといった方法があります。その辺りはここでよくまとめられています。決済周りは時間がかかっても確定した結果を出せるのが重要だと思うのですが、もっと良さそうな方法がないか考えてみます。

ショッピングカートと注文が似ているのですが、これを同じドメインクラスとして扱えないか考えてみます。そのためにはショッピングカートなのか注文なのか区別できる必要があるのですが、その手段として注文の状態として未注文を加えることで解決できるか考えてみます。そうしたら注文の状態として未注文、注文済み、注文キャンセルを持たせてみます。 それから、未注文の場合はショッピングカートと同様の動きをするので商品の追加等の操作が行われたら未注文状態の注文に商品を追加します。

注文の支払い状況はどこで管理するか

現在のドメインモデルの支払い方法の部分に支払い済みかどうか確認するためのメソッドを追加すれば良さそうに思います。ただし支払い方法だと責務が異なると思うので支払い状況に名前を変更したいと思います。

カートに入れた商品の金額が変わったことをどう確認するか

現在の品目のクラスはショッピングカートに追加した時点での金額で、ここでは品目の内容と最新の金額を保持した商品クラスの内容をどう比較するかになります。簡単に思いつく対応方法として品目の集約に商品を加えて品目にカート追加時の金額で計算したものと最新の金額で計算したものを返すなどあるかもしれませんが、この方法だと注文の集約に商品の集約が含まれる形になります。注文の集約から商品は参照するだけにしたらデットロックなどの心配はないと思いますが別の方法で考えたいと思います。

集約間を疎結合に保てる様にするための方法として商品追加時の金額で計算した品目のリストと現在の金額で計算した品目をそれぞれ用意しておいて金額の変更はその差分を見るといった方法があると思います。カート追加時の品目と現在の金額の品目で比較をしやすい様にデータをどう持つかは考える必要がありそうです。

商品の確認画面では商品の名称等の情報があった方が良い気がしますが、ドメインモデルは登録、更新、削除周りで必要そうなものがあれば良い気がするので必要だと感じた時に足せるくらいには考えておきます。参照系と登録、更新、削除を分けるはCQRSの考えでDDDとCQRSで検索したら色々出てくると思います。

それから、商品の詳細情報がドメインモデルから省かれていましたが商品に直接持つには余計な気がするので、商品詳細のクラスを追加して商品に集約させます。

見直した後のドメインモデルは以下の様になりました。 f:id:steavevaivai:20190120174200p:plain

クラス図の作成

ドメインモデルが纏まったら次はクラスに対して属性やメソッドを追加したクラス図を作成します。メソッドまで書くのがベストだと思いますが、とりあえず属性だけ足しておいて以下の様にしたいと思います。 f:id:steavevaivai:20190203123524p:plain 実際必要な属性がこれでは少なかったり、定義した属性は問題(講座の型がStringとか)があると思いますが、ユースケース駆動開発を学ぶのを目的としてこれで進めてみたいと思います。 後、これはクラス図であってデータベースの定義ではないことが注意が必要です。DB上にはパスワードを保持していると思いますが、クラスとして保持する必要が現状なさそうなので省いています。

ER図の作成

ユースケース駆動開発実践ガイドには含まれていなかったと思うのですが、このタイミングでDB上でデータをどう保持するか考えて良いのかと思います。今回のクラス図であれば以下のER図で良いかと思います。 f:id:steavevaivai:20190120212610p:plain クラス図とER図は一致しないと思うのですが、例えばクーポンの使用状況についてクラス図は直接クーポンを注文に集約させていますが、ER図ではused_couponテーブル経由で使用するクーポンを参照する様にしています。クーポン自体は注文の集約上で更新するのはおかしいと思うのでクーポンにorder_idの属性を持たせるのではなく、used_couponにデータをインサートする様にしています。

ユースケース駆動開発実践_分析・概念設計

ユースケースモデリングにより以下のユースケース記述が作られたとして、次は分析・概念設計を進めたいと思います。

1.ログインする
 ユーザはログイン画面を表示しユーザ名、パスワードを入力してログインボタンをクリックする。
 ログインに成功したら商品一覧画面を表示する。ログインに失敗したらログイン失敗と表示する。

2.商品を検索する
 ユーザは商品一覧画面で商品名を指定して商品を検索する。
 商品名での検索結果の上位20件を一覧で返して表示する。

3.商品詳細を確認する
 商品一覧画面から商品名をクリックする。
 選んだ商品の詳細画面を表示する

4.商品をカートに追加する
 商品の詳細画面からカートに追加をクリックする。
 システムはユーザに紐づくショッピングカートに商品を追加する
 
5.ショッピングカート画面を確認する
 ユーザはショッピングカート画面にアクセスする。
 商品の品目、合計金額が表示されたショッピングカート画面を表示する。この時前回ショッピングカート画面を表示した時から金額の変更があったものはそれを通知するメッセージをだす。

6.商品を購入する
 ユーザはショッピングカート画面で購入ボタンをクリックする。
 支払い方法、届け先住所、クーポンコードの入力項目がある注文内容入力画面を表示する。この時、クレジットカードと届け先住所は個人情報から取り出して選択候補として選べるようにしておく。
 ユーザは支払い方法、届け先住所それから任意でクーポンコードを入力し、注文内容確認ボタンをクリックする。
 支払い方法と、届け先住所をチェックして問題ないことを確認する。クーポンコードの入力がある場合は、それを合計金額に反映する。それから注文内容(品目、支払い方法、届け先、クーポン入力、合計金額)が表示された注文内容確認画面を表示する。
 ユーザは注文確定ボタンをクリックする。
 システムは注文内容を確定する。この時支払い方法毎で以下の処理を行う。
  - 支払い方法がクレジットカードの場合は注文確定の際に外部システムのクレジットカードの決済処理を呼び出す。支払いが行えたら、支払い状況を支払い済みに更新する。
  - 支払い方法が銀行振込の場合は、外部システムで振込用のワンタイム口座を作成して注文確定画面に口座の情報、支払い期限を表示する
 

ロバストネス分析

それではユースケースの内容からロバストネス図を作成していきたいと思います。
まずログイン処理についてロバストネス図を作成します。 f:id:steavevaivai:20190120112906p:plain ユースケースの内容をそのまま貼り付けていってロバストネス図を作成します、例外処理をどのタイミングで行うかも把握できる様にします。

続いて商品検索のロバストネス図です。 f:id:steavevaivai:20190120115745p:plain

次に商品詳細を確認するです。 f:id:steavevaivai:20190120120421p:plain

商品をカートに追加する f:id:steavevaivai:20190120121608p:plain

ショッピングカート画面を表示する f:id:steavevaivai:20190120122228p:plain

商品を購入するのロバストネス図 f:id:steavevaivai:20190120125832p:plain

このようにロバストネス分析を行うことでユースケースモデリングでは行えなかったドメインオブジェクトと処理の関連づけとエラー処理周りの方針がたつかと思います。これをもとに紙芝居を作ることで顧客への説明もしやすくなると思います。 ロバストネス分析中に必要なドメインクラスの追加や修正がたびたび発生するので、そのつどでドメインモデルの更新を行った方が良いです。

ユースケース駆動開発実践_要件定義

前回ユースケース駆動開発の流れを掴めたので実際に開発を進めてみたいと思います。以下のECサイトの機能要求からドメインモデリングユースケースを行いたいと思います。

要求定義

・ユーザはログイン画面でユーザ名、パスワードを入力してパスワード認証が行える。
・ユーザはショッピングカートに商品を入れることができる。
・ユーザはショッピングカートに入れた商品の品目を参照できる。
・ユーザはショッピングカートに入れた商品の品目を更新できる。
・ユーザはショッピングカートに入れた商品を購入できる。
・ECサイト管理者は商品カタログの登録、更新が行える。
・商品カタログ上の商品が購入対象になる。
・商品の金額に変更があった場合、ショッピングカートの参照画面で変更前と後の金額を表示する。
・商品の支払方法としてクレジットカードと銀行振込を選択できる。
・ECサイト管理者は1回限り利用できるクーポンコードを発行できる。
・ユーザは商品の購入手続き時にクーポンコードを入力できる。
・クレジットカード支払いの場合、購入時に支払いが行われて配達予定日を表示する。
・銀行振込の場合、購入後1週間が支払い期限になりそれがすぎるど購入キャンセルになる。振込が完了したら配達予定日が確認できる様になる。
・支払い手続きが完了したら外部のシステムに連携して商品を発送予定にします。
・ユーザは商品の配送状況を確認する。このとき配送状況は外部システムと連携して取得する。
・購入画面で宛先を選択する。宛先は個人情報、または手入力で入力する。
・商品の発送前であればキャンセルが行える。入金後のキャンセルでは外部システムと連携し払い戻しが行われる。
・ユーザは購入履歴を確認できる。   

機能要求の一覧はドメインクラスの豊富な情報源となる。ドメインモデルのクラスの抽出をするためにも機能要求の名詞を抽出します。
ユーザ ショッピングカート 商品 品目 ECサイト管理者 商品カタログ 支払方法 クレジットカード 銀行振込 クーポンコード 外部配送システム 配送状況 個人情報 購入履歴
このときドメインモデルのクラスとして扱うには小さそうなのは省きます。ユーザ名はユーザアカウントの属性として扱えそうなので省きます。

機能要求と合わせてアクターも抽出します。このシステムのアクターはユーザ ECサイト管理者 外部配送システム となります。このとき、アクターのユーザとドメインクラスのユーザは別であることに注意します。

ドメインモデリング

次に、機能要求から抽出した名詞の一覧の関係を図示化します。 f:id:steavevaivai:20190120102452p:plain ドメインモデリングでは凡化、集約の関係が明らかになる様にする。ドメインモデリングの対象はドメインなのでGUI固有の部品クラス等は含まない様にします。商品カタログと商品はとりあえず関連がない状態ですが、ecサイト上で出店ができる様になった場合出店者アカウント毎の商品カタログを用意する必要性が出てくるのでその場合は商品がどのカタログに関連づいているかの情報が必要になってくると思います。

ユースケースモデリング

次にアクター毎の操作のユースケース図を描きます。ユースケース図では、事前に必要な操作、とその操作を実施することで発生するものなどがわかる様に書きます。例えば商品の検索はログインしていなくてもできるけど、ショッピングカートへの品目追加はログインが必要ということがわかる様にします。 f:id:steavevaivai:20190119212202p:plain
precedesは事前に必要な操作になります、ショッピングカートへの品目の追加は事前にログインが必要なのでprecedesになります。invokesはその操作により発生するものを指します。ここでは購入後の入金に発生する商品の発送がinvokesになります。

また、ユースケース図だけでなく実際の操作の流れを叙述的に文章に書き込みます。
例えばユーザはログイン画面でユーザ名、パスワードを入力してパスワード認証が行える。という操作は以下の様になります。

  • ユーザはログイン画面でユーザ名パスワードを入力してログインボタンをクリックする
    • ログインに成功したら商品の一覧画面を表示する
    • ログインに失敗したらログイン画面にログイン失敗と表示する

要求レビュー

ドメインモデルのオブジェクト間の関係を説明する。ユースケースについては正常系とエラー系それぞれ叙述的に書かれていることを確認する。ユースケース記述にはGUI紙芝居や画面モックアップを使って説明する。

ユースケース駆動開発実践ガイドメモ

ユースケース駆動開発実践ガイドの読書メモ

ICONIXプロセストとは

ドメインモデリングによって対象領域を理解し、ユースケースを書くことで顧客の要求を目に見える形でまとめ上げ、ロバストネス図によってソフトウェアの振る舞いを明確化し、シーケンス図によって振る舞いをオブジェクトに割り当てる。ロバストネス分析によりソフトウェアの振る舞いを具体化することで顧客との要求とのズレが起きないようするというプロセスがICONIXには組み込まれている。 ICONIXプロセスではユースケースからソースコードを作っており、小さなユースケース群毎での開発・テストが行えるためアジャイルプロジェクトに適合しやすい。

ユースケースから曖昧さを排除し具体的で明確にする

ICONIXプロセス

ICONIXプロセスは以下の工程で行われる

要件定義

ドメインモデリング

ITプロジェクトではコミュニケーションでの齟齬が発生しやすい。例えばある人が"書籍のレビュー"と言ったとき、ある人は"編集者レビュー"だと思い、他のある人は"顧客レビュー"だと解釈すると言ったことが発生する。この様な誤解はドメインモデリングによりプロジェクト内の言語を統一することが問題解決に役立ちます。

  • ドメインモデルとは何か?
    • プロジェクトの用語集
    • プロジェクトメンバー同士とのやり取りの中で生まれる
  • ドメインモデリングのポイント
    • プロジェクト中で使用する言葉の統一が行えるので最初の方で行った方が良い
    • オブジェクト同士の凡化・集約を図示化する。
    • ドメインモデルとデータモデル、データベースのテーブルを混同しない
    • ドメインに関係するものだけをモデリングする。
    • 小さすぎる情報は一つのモデルとして独立させない

オブジェクト同士の凡化・集約を図示化する。

凡化、集約はドメインモデルで重要なクラス間の関係で、クラス間の関係の95%はこれで表せられるらしい。一番最初のドメインモデルを書くときはクラスの属性などの詳細は考えなくても良い。

ドメインモデルとデータモデル、データベースのテーブルを混同しない

ドメインモデルとデータベースのテーブルなどは常に一致しているわけでなくインピーダンスミスマッチが生じることもある。

どうやってモデリングをするのか?
要件の一覧からモデルとして使える名詞を抜き出してモデル間の関係、集約を簡単に書いてみる。それからメンバー間でのレビューを通してオブジェクト間の関係、集約に齟齬がなくなるようにする。この時点では詳細な内容まで書こうととしないで大枠を捉えられるようにする。

ドメインに関係するものだけをモデリングする。

ドメインモデルはドメイン領域のみを対象としてモデリングする。問題を最適化するためのヘルパークラスなどは対象外。

小さすぎる情報は一つのモデルとして独立させない

属性などの情報は小さすぎるのでドメインモデルのクラスにする

ユースケースモデリング

プロジェクトの最初の方に出される機能要求は思いつきなどで書かれたことが多く、そこから見積もりを作成を作ることは不安要素が多く困難です。最初の機能要求だけでなく顧客との会話や紙芝居を使った説明を通してユースケースモデリングを行うことで不安要素をなくすことができます。

アクターとユースケース図を使う

開発対象システムが外部とどうやり取りするかを具体化する。この時のシステムの外部からアクセスするのがアクターでほとんどはユーザ以外にも、外部システムなどもアクターに含まれます。

アクターとシステムの対話を具体的に書く

例えばログイン認証について、 ユーザはパスワードを使った認証方式でログインできる。 と言った機能要求があったとする。これだけだとユーザが行う操作、システムの振舞いが不明瞭なので以下のように具体化する。 ユーザはユーザ名とパスワードを入力して「ログイン」ボタンをクリックする。システムはユーザ名からユーザの情報を取り出し、パスワードをチェックする。パスワードが一致していたらユーザはシステムにログインする

ユースケースは実行時の振る舞いの仕様書である

上記例のようにユースケースは実行時の振る舞いを具体化するのが肝である。ICONIXプロセスではユースケース毎でシーケンス図を活用することでオブジェクトの振る舞いを明確化する。

マイルストーン1/要件レビュー

ユースケースモデリングまで完了したところで顧客を加えてレビューを行い認識合わせをする

  • 要件レビューのポイント
    • ドメインモデルに記述されている言葉が顧客にも理解できるかを確認する
    • ドメインモデルが、ドメインオブジェクトの関係を示していることを確認する
    • ユースケースがオブジェクトモデルの用語で記述されていることを確認する
    • GUIのプロトタイプや紙芝居でより具体的なイメージが伝わるようにする

ドメインモデルに記述されている言葉が顧客にも理解できるかを確認する

大枠の部分でドメインモデルが正しく、用語がレビュアーに伝わることを確認する。詳細なモデルはユースケースの分析を通して改善していく

ドメインモデルが、ドメインオブジェクトの関係を示していることを確認する

ドメインオブジェクト間での凡化集約関連などの関係がわかるようにする。

ユースケースがオブジェクトモデルの用語で記述されていることを確認する

ユースケースの曖昧さの大部分はしばしばユーザの用語でユースケースが記述されることから発生する。曖昧さを排除するためにも適切なドメインオブジェクトを使って書く。

GUIのプロトタイプや紙芝居でより具体的なイメージが伝わるようにする

全てのユーザの振る舞いを確実にユースケース中で説明するようにする。時間がかからないレベルで画面のモックを作ることでユーザが取りうる行動を調査するための手助けとなる。

分析/概念設計

ロバストネス分析

ロバストネス分析ではユースケース記述を分析し、ユースケースごとにオブジェクト群を推定する。

  • バウンダリオブジェクト
     システムと外部とのインターフェース。webページなど
  • エンティティ
     ドメインモデル上のクラス
  • コントローラ
     バウンダリオブジェクトとエンティティの間をとりもつ操作

ロバストネス図で以下のように表す。 f:id:steavevaivai:20180430161828p:plain

ユースケース記述をロバストネス図に直接貼り付ける

ユースケース図をそのままロバストネス図にする。例えばログインは以下のようになる。 f:id:steavevaivai:20190119161019p:plain

ロバストネス図作成中でもユースケース図を修正する

ロバストネス図作成中にユースケースを1文ずつ見直すことで曖昧さを解消できることがある。そのためロバストネス図作成と並行してユースケース図の修正も行う。

画面ごとでバウンダリオブジェクトを作成する

ロバストネス図を描くことによって、バウンダリオブジェクトに対する明確な命名を行うことができる。

ロバストネス分析は概念設計であり詳細設計ではない

ロバストネス分析では、実際の設計を行う前の振る舞い要求を検証する目的で概念設計を行う。ロバストネス分析を行うことにより正確で明確なユースケースを書く能力が磨かれる。

予備設計レビュー

ロバストネス図ドメインモデル、およびユースケース記述があっていることを確認して詳細設計に橋渡しを行う間のフェーズ。レビューの参加者は顧客、開発チーム、マネージャ。

  • ユースケース記述とロバストネス図が一致しているかどうか確認する
  • ロバストネス図上の全てのエンティティが更新後のドメインモデル上に存在させる
  • 代替コース(エラー処理、バリデーションなど)が漏れていないか、代替コースに対する振る舞いが記述されているか確認する
  • ユースケースがオブジェクトモデルとGUIの用語で記述されていること
  • シーケンス図のレベルの詳細な内容にはなっていないこと

ロバストネス図上の全てのエンティティが更新後のドメインモデル上に存在させる

オブジェクトの発見こそがロバストネス分析の主目的の一つなので、見つけたらドメインモデルにも反映しないとあまり意味がない。

代替コース(エラー処理、バリデーションなど)が漏れていないか、代替コースに対する振る舞いが記述されているか確認する

最初のユースケースで漏れがちな想定外のケースについても具体化、明確化する。

ユースケースがオブジェクトモデルとGUIの用語で記述されていること

ドメインオブジェクトや画面の取り扱いで名前の曖昧さを排除しておけば多くの問題が解決できる

詳細設計

シーケンス図

ロバストネス分析、予備設計レビューを完了し詳細設計に進む準備ができました。予備設計はクラスを発見するための作業で詳細設計では振る舞いの割り当てを行います。

  • シーケンス図作成のポイント
    • なぜシーケンス図を描くか理解する
    • ロバストネス分析を完了させたところから始める
    • シーケンス図はオブジェクトがどのように働くかを示す道具として使う
    • コーディングを始める前にシーケンス図上に書かれた設計をプレリファクタリングする

なぜシーケンス図を描くか理解する

シーケンス図作成ではクラスに対する責務の割り当てユースケースの生存期間中に各クラスが他のクラスとどの様に相互作用するかの提示クラスに対する操作の割り当ての完了を目標としている

ロバストネス分析を完了させたところから始める

ロバストネス図作成によりオブジェクト間の相互作用が識別できる様になった。シーケンス図はロバストネス図の概念設計よりもさらに具体的かつ詳細な設計になる。

シーケンス図はオブジェクトがどのように働くかを示す道具として使う

シーケンス図の「目的」とは、相互作用するオブジェクトに振る舞いを割り当てること

ambariでhadoopクラスタ構築

Ambariとは

OSSで作られているHadoop管理プラットフォーム。セキュアな(Kerberosベースの) Hadoopクラスタのインストールをサポートし、ロールベースのユーザー認証と許可そして監査機能を提供し、ユーザー管理のために LDAPActive Directoryとの統合も提供している。管理対象のホストにHDPのインストールを行うことができる。

HDPとは

Hortonworksが提供するHadoopエコシステムになります。
https://jp.hortonworks.com/ecosystems/

Ambariインストール

公式サイトを確認しAmbariインストール用のリポジトリをインストールする。

wget -nv http://public-repo-1.hortonworks.com/ambari/centos7/2.x/updates/2.6.0.0/ambari.repo -O /etc/yum.repos.d/ambari.repo

Ambari-serverインストール

yum install ambari-server

[root@ambari01 ~]# yum search ambari-server
読み込んだプラグイン:fastestmirror
Loading mirror speeds from cached hostfile
 * base: ftp.riken.jp
 * extras: ftp.riken.jp
 * updates: ftp.riken.jp
========================== N/S matched: ambari-server ==========================
ambari-server.x86_64 : Ambari Server

  Name and summary matches only, use "search all" for everything.
[root@ambari01 ~]# yum install ambari-server
読み込んだプラグイン:fastestmirror
Loading mirror speeds from cached hostfile
 * base: ftp.riken.jp
 * extras: ftp.riken.jp
 * updates: ftp.riken.jp
依存性の解決をしています
--> トランザクションの確認を実行しています。
---> パッケージ ambari-server.x86_64 0:2.2.2.0-460 を インストール
--> 依存性の処理をしています: postgresql-server >= 8.1 のパッケージ: ambari-server-2.2.2.0-460.x86_64
--> トランザクションの確認を実行しています。
---> パッケージ postgresql-server.x86_64 0:9.2.23-3.el7_4 を インストール
--> 依存性の処理をしています: postgresql-libs(x86-64) = 9.2.23-3.el7_4 のパッケージ: postgresql-server-9.2.23-3.el7_4.x86_64
--> 依存性の処理をしています: postgresql(x86-64) = 9.2.23-3.el7_4 のパッケージ: postgresql-server-9.2.23-3.el7_4.x86_64
--> 依存性の処理をしています: libpq.so.5()(64bit) のパッケージ: postgresql-server-9.2.23-3.el7_4.x86_64
--> トランザクションの確認を実行しています。
---> パッケージ postgresql.x86_64 0:9.2.23-3.el7_4 を インストール
---> パッケージ postgresql-libs.x86_64 0:9.2.23-3.el7_4 を インストール
--> 依存性解決を終了しました。

依存性を解決しました

================================================================================
 Package             アーキテクチャー
                              バージョン         リポジトリー              容量
================================================================================
インストール中:
 ambari-server       x86_64   2.2.2.0-460        Updates-ambari-2.2.2.0   409 M
依存性関連でのインストールをします:
 postgresql          x86_64   9.2.23-3.el7_4     updates                  3.0 M
 postgresql-libs     x86_64   9.2.23-3.el7_4     updates                  234 k
 postgresql-server   x86_64   9.2.23-3.el7_4     updates                  3.8 M

トランザクションの要約
================================================================================
インストール  1 パッケージ (+3 個の依存関係のパッケージ)

総ダウンロード容量: 416 M
インストール容量: 478 M

ambari-serverセットアップ
1.selinuxを無効化する

# vi /etc/sysconfig/selinxu
SELINUX=disabled

2.ambari-server setupを実行する

[root@ambari01 ~]# ambari-server setup
Using python  /usr/bin/python
Setup ambari-server
Checking SELinux...
SELinux status is 'disabled'
Customize user account for ambari-server daemon [y/n] (n)? y
Enter user account for ambari-server daemon (root):ambari
Adjusting ambari-server permissions and ownership...
Checking firewall status...
Redirecting to /bin/systemctl status iptables.service
Unit iptables.service could not be found.

Checking JDK...
[1] Oracle JDK 1.8 + Java Cryptography Extension (JCE) Policy Files 8
[2] Oracle JDK 1.7 + Java Cryptography Extension (JCE) Policy Files 7
[3] Custom JDK
==============================================================================
Enter choice (1): 1
To download the Oracle JDK and the Java Cryptography Extension (JCE) Policy Files you must accept the license terms found at http://www.oracle.com/technetwork/java/javase/terms/license/index.html and not accepting will cancel the Ambari Server setup and you must install the JDK and JCE files manually.
Do you accept the Oracle Binary Code License Agreement [y/n] (y)? y
Downloading JDK from http://public-repo-1.hortonworks.com/ARTIFACTS/jdk-8u60-linux-x64.tar.gz to /var/lib/ambari-server/resources/jdk-8u60-linux-x64.tar.gz
jdk-8u60-linux-x64.tar.gz... 22% (38.9 MB of 172.8 MB)

JavaにはJCEを利用しています。Javaがデフォルトで使っている暗号化の鍵はAESの128bitになるのですがkerberos認証を有効にする場合は256bitのAESが使えるJCEを利用します。

3.ambari-serverを起動する

ambari-server start

4.8080ポート解放

firewall-cmd --add-port=8080/tcp --zone=public --permanent
firewall-cmd --reload

5.起動状態確認

[ambari@ambari01 ~]$ ambari-server status
Using python  /usr/bin/python
Ambari-server status
Ambari Server running
Found Ambari Server PID: 1756 at: /var/run/ambari-server/ambari-server.pid

以下のURLにアクセスしてambariにアクセスする。初期のユーザーID/パスワードはadmin/adminになっている。
http://ホスト名:8080/
初回のログイン後は以下のような画面が表示される
f:id:steavevaivai:20180428230948p:plain

6.Launch Install Wizardを実行する
事前にambariサーバからambariの起動ユーザでhdpインストール先のホストにパスワードなしのssh鍵で接続できるようにしておく。confirm hostで止まる場合は 、host名を修正しhostsファイルで参照できるようになっているか 、hostsファイルの内容がクラスタ内で同一になっているか、ホスト名がトップレベルのドメインになっていないか、クラスタ内はntpで時間の同期が取れているかを確認する。それでも入らない場合は最新のAmbariがないか確認する。
1. cluster nameを入力する 2. HDPのバージョンを指定する 3. install option
- ホスト名の入力
- 秘密鍵と接続ユーザの登録
4. confirm hostは完了したらnextをクリックする 5. choose servicesはそのままnextをクリックする 6. Assign metricsもnextをクリックする 7. Assign service and clientsでnextをクリックする 8. customize serviceでは必須入力を入力してnextをクリックする 9. それから各ホストにhdpのインストール、テストが始まります。

インストール完了後のambariの画面は以下のようになります。とりあえずhdfsとhiveだけサービスを起動させているのでアラートがたくさん出ています。 f:id:steavevaivai:20180428230943p:plain

hdfsにアクセスしてみる

hdpをインストールしたホストにて以下コマンドを実行してhdfsにアクセスできることを確認します。 ユーザはambariインストールに使用したユーザを使っています。

[ambari@hadoop-slave01 ~]$ hdfs dfs -ls /
Found 11 items
drwxrwxrwx   - yarn   hadoop          0 2018-04-28 21:22 /app-logs
drwxr-xr-x   - hdfs   hdfs            0 2018-04-28 21:52 /apps
drwxr-xr-x   - yarn   hadoop          0 2018-04-28 21:22 /ats
drwxr-xr-x   - hdfs   hdfs            0 2018-04-28 21:22 /hdp
drwxr-xr-x   - mapred hdfs            0 2018-04-28 21:22 /mapred
drwxrwxrwx   - mapred hadoop          0 2018-04-28 21:23 /mr-history
drwxrwxrwx   - spark  hadoop          0 2018-04-28 22:10 /spark-history
drwxrwxrwx   - spark  hadoop          0 2018-04-28 22:10 /spark2-history
drwxrwxrwx   - hdfs   hdfs            0 2018-04-28 21:56 /tmp
drwxr-xr-x   - hdfs   hdfs            0 2018-04-28 21:52 /user
drwxr-xr-x   - hdfs   hdfs            0 2018-04-28 21:23 /webhdfs

hiveにアクセスしてみる

beelineコマンドを実行してhiveにアクセスできることを確認してみます。

[ambari@hadoop-slave01 ~]$ beeline
Beeline version 1.2.1000.2.6.3.0-235 by Apache Hive
beeline> !connect jdbc:hive2://localhost:10000/default
Connecting to jdbc:hive2://localhost:10000/default
Enter username for jdbc:hive2://localhost:10000/default: ambari
Enter password for jdbc:hive2://localhost:10000/default: ******
Connected to: Apache Hive (version 1.2.1000.2.6.3.0-235)
Driver: Hive JDBC (version 1.2.1000.2.6.3.0-235)
Transaction isolation: TRANSACTION_REPEATABLE_READ
0: jdbc:hive2://localhost:10000/default> show databases;
+----------------+--+
| database_name  |
+----------------+--+
| default        |
+----------------+--+
1 row selected (3.666 seconds)
0: jdbc:hive2://localhost:10000/default> use default;
No rows affected (1.73 seconds)
0: jdbc:hive2://localhost:10000/default> show tables;
+-----------+--+
| tab_name  |
+-----------+--+
+-----------+--+
No rows selected (0.71 seconds)

個別にhadoop, hiveのインストールを行うのは大変なのでambariなどでまとめて入れて管理するのが良さそうです。

sbtのマルチプロジェクトでサブプロジェクトをimportする

以下のようにcoreとsubのマルチプロジェクトを作成しsubプロジェクトのSampleクラスをcoreプロジェクトのCoreクラスでimportして利用してみたいと思います。

.
├── build.sbt
├── core
│    ├── src
│    │   ├── main
│    │   │   ├── resources
│    │   │   └── scala
│    │   │       └── core
│    │   │           └── Core.scala
│    │   └── test
│    └── target
└── sub
     ├── project
     ├── src
     │   ├── main
     │   │   ├── resources
     │   │   └── scala
     │   │       └── sub
     │   │           └── Sample.scala
     │   └── test
     └── target

build.sbt では以下のように project in file によりプロジェクトのディレクトリを指定します。サブプロジェクトをインポートできるようにするに はdependsOnで参照先のプロジェクトを指定します。

lazy val commonSettings = Seq(
  version := "0.1.0-SNAPSHOT",
  scalaVersion := "2.11.8"
)

lazy val core = (project in file("core"))
  .settings(commonSettings)
  .dependsOn(sub)

lazy val sub = (project in file("sub"))
  .settings(
    commonSettings,
    libraryDependencies ++= Seq(
    )
  )

coreプロジェクト内でSampleクラスをimportする場合は通常通り import sub.Sample で大丈夫です。

sbtコンソール内にてプロジェクトを一覧、切り替えて実行する場合は以下のようになります。

sbt:core> projects
[info] In file: ****/scala/workspace/study_scala/
[info]   * core
[info]     sub
sbt:core> project core
[info] Set current project to core (in build file: ****/scala/workspace/study_scala/)
sbt:core> runMain core.Core