DAG (Directed acyclic graph) - 有向非巡回グラフ
先日、JJUG CCC 2015 Fallに参加して、灰色のサイトで有名なひしだまさん(ひしだま (@hishidama) | Twitter)の「GH-6 Java8 Stream APIとApache SparkとAsakusa Frameworkの類似点・相違点」を聞いた際に出てきた「DAG (Directed acyclic graph) - 有向非巡回グラフ」が面白そうだったので調べてみた。
DAGとは
Wikiによると、
グラフ理論における閉路のない有向グラフの事。
有向グラフは頂点と有向辺(方向を示す矢印付きの辺)からなり、辺は頂点同士をつなぐが、ある頂点 v から出発し、辺をたどり、頂点 v に戻ってこないのが有向非巡回グラフである。
となっている。
DAGの例
Wikiの例はわかりづらいので、JJUG CCC 2015 Fallのひしだまさんのスライドを見ると、18枚目に例が載っている。
左は向きがない(無向)なのでDAGではない。
中央は菱型の条件分岐で上に戻っている(巡回)のでDAGではない。
右は向きがあって(有向)条件分岐があるが循環していない(非巡回)のでDAGであると言える。
ひしだまさんの発表は、DAGで例題を示し、Stream APIとSparkとAsakusaFWの相違点について説明するという流れになっていた。
説明も聞いていたが、自分はこのDAGが気になってしまって、心の半分がDAGに持って行かれていたことは内緒としておきたい・・・。
DAGの活用
DAGを使うと、今まで言葉でしか説明していなかったものや、なんとなく図式化していたものを明確に説明できるのではないだろうか。
例えば
社員一覧画面は、社員マスタからデータを取得し表示する。
ただし所属は所属マスタから取得し表示すること。
のようなものは下記のように表せる。
うん。わかりやすい。
もう少し複雑な例だと日締め処理のバッチなど。
その日一日の取引内容を集計する。
集計結果はPDF化し担当部署長宛にメールにて通知する。
のようなものは下記のように表せる。
ここまでやってて、もっとDAGを用いるのに適した分野があることに気付いた。
そう、料理のレシピだ。
DAGを用いた料理のレシピ
例えばみんなのきょうの料理に掲載されている「かじきのガーリック炒めレシピ」だと、このようになる。
ちなみにカレーのレシピはこのようになる。
このように、DAGの応用範囲は広いということがわかった。
料理のレシピはDAGでも併せて記述するべきだと思う。
JavaのStream API(IntStream)を触ってみた
調子に乗ってIntStreamも使ってみた。
UNIX/Linux系アプリにありがちな、実行時引数に「-」を使って色々なパラメータを渡すアレを受け取る処理を書いてみた。
例題として以下のパラメータを使用する。
- -c → 設定ファイルの指定
- -i → 入力ファイルの指定
- -o → 出力ファイルの指定
例えば以下のようにパラメータを指定する。
FooApplication -c configFile -i inputFile -o outputFile
当然、順番を変えて以下のように指定しても同じ意味となる。
FooApplication -i inputFile -c configFile -o outputFile
Javaの場合、mainメソッドの引数「String[] args」にそれぞれの値が配列に格納されて渡ってくるので、今回はそれをIntStreamを使って処理をする。
処理結果はMapに詰めて、キーにスイッチの値( -c など)を、バリューにそのスイッチの値 (configFile)を詰めることにする。
コードで書いた処理結果のイメージは以下の通り。
Map<String, String> map = new HashMap<>(); map.put("-c", "configFile"); map.put("-i", "inputFile"); map.put("-o", "outputFile");
Stream APIを使わなかった場合は以下の通り。(諸々のエラー処理は省略)
Map<String, String> map = new HashMap<>(); for (int i=0; i<args.length; i++) { if (args[i].startsWith("-")) { map.put(args[i], args[i+1]); } }
これがStream APIを使うと以下の通り。(諸々のエラー処理は省略)
Map<String, String> map = new HashMap<>(); IntStream.range(0, args.length) .filter(i -> args[i].startsWith("-")) .forEach(i -> map.put(args[i], args[i+1]));
・・・たぶん、IntStreamはこういう使い方をするためのものではないよね。
うん。知ってて使ってみた。
JavaのStream APIを触ってみた
読み込んだテキストファイルのうち「#」で始まる行と空行を除外するという処理を、せっかくだからStream APIで実装してみた。
Stream APIを使わなかった場合は以下の通り。
List<String> result = new ArrayList<>(); // 対象のテキストファイルを読み込んで、各行の内容を含んだListを受け取る List<String> lines = readAllLines(); for (String line: lines) { // #から始まる行は読み飛ばす if (line.trim().startsWith("#")) { continue; } // 空行も読み飛ばす if (line.trim().length()==0) { continue; } result.add(line); }
これがStream APIを使うと以下の通り。
// 対象のテキストファイルを読み込んで、各行の内容を含んだListを受け取る List<String> lines = readAllLines(); List<String> result = lines.stream() .filter(line -> !(line.trim().startsWith("#"))) // #から始まる行は読み飛ばす .filter(line -> !(line.trim().length()==0)) // 空行も読み飛ばす .collect(Collectors.toList());
実にシンプル。
これは気持ち良いかも。
for文禁止にしたい気持ちが、今更ながらわかってきた。
Gitの設定ファイル
以下のサイトを見ながら自分の言葉でまとめてみた。
Git - Git の設定
Gitの設定方法
下記のようにgitコマンドを使ってGitの設定を変更することが出来る。
git config [設定先] [設定項目] [設定値]
例えばユーザー名として「John Doe」を設定するなら、設定項目が「user.name」、設定値が「John Doe」となり、コマンドは以下のようになる。
git config --global user.name "John Doe"
これでグローバル域にユーザー名「John Doe」を設定したことになる。
あとは、設定先を切り替えることで使いやすい設定にカスタマイズすれば良い。
続いてその設定先についてまとめる。
3段構えの設定先
Gitの設定先は3段構えとなっている。
- システム:インストールしたGit本体に対する設定
- グローバル:ユーザー単位での設定
- ローカル:リポジトリ単位での設定
上から順にスコープが狭くなる。
それぞれを掘り下げてみてみる。
システム
インストールしたGit本体に対する設定で、Linux系だと下記のファイルに設定内容が書き込まれる。
/etc/gitconfig
Windowsの場合はGitのインストールフォルダの下記のファイルに設定内容が書き込まれる。
[インストールフォルダ]/etc/gitconfig
gitコマンドはまずこのファイルに記載した設定を読み込む。
[設定先]に使用するパラメータは「--system」となり、以下のように設定する。
git config --system core.autocrlf true
まとめ
3つの設定をまとめると下記のようになる。
git config --system [システムへの設定] git config --global [グローバルへの設定] git config --local [ローカルへの設定]
設定できる項目や設定に関することについては冒頭のGitのサイトや下記コマンドで表示するマニュアルページに詳しくあるので、そちらを参照。
git config --help
GitBucket 3.2 を試す
「Scalaで実装されたオープンソースのGitHubクローン」のGitBucketのインストールが予想以上に簡単だったので、メモを残す。
今回入れるGitBucketの3.2。
GitBucketのサイトは下記の通り。
https://github.com/takezoe/gitbucket
事前準備
Scalaで実装されているので、Javaが入っていないとならない。
今回はJava8(Update31)で試した。
(Javaのインストールフォルダのbinフォルダにパスを通しておきましょう)
続いてGitBucket本体のダウンロード。
GitBucketのwarファイルをGitBucketのサイトからダウンロードする。
GitBucket 3.2は下記URLでダウンロードできる。
https://github.com/takezoe/gitbucket/releases/download/3.2/gitbucket.war
準備に必要なのはこの2点。
GitBucketの実行
GitBucketの実行は下記のコマンドで行う。
java -jar gitbucket.war
これだけ。
GitBucketには組み込みWebサーバのJettyを使っているため、上記のようにJARファイルを実行するとGitBucketをデプロイ済みのWebサーバーが起動する。
GitBucketへアクセスする
ブラウザを起動して、起動したサーバーもしくはローカルホストのポート8080にアクセスする。
http://localhost:8080/
以下のようなログイン画面が表示されれば正常に起動したことになる。
あとは初期ユーザroot(パスワードもroot)でログインして、ユーザ追加やグループの追加等の設定を行う。
その他
GitBucketのサイトにある通り、javaコマンドで起動する際にパラメータを与えることが出来る。
自分が真っ先に行ったのは「--gitbucket.home」の設定。
初期状態では実行ユーザのホームディレクトリにGitBucketの設定とリポジトリが作成される。
ホームディレクトリ以外をGitBucketの作業場所とするには、下記のように起動パラメータを与える。
java -jar gitbucket.war --gitbucket.home=D:\GitBucket
起動すると、D:\GitBucketに設定内容を保存したデータベースファイル(H2かな?)とリポジトリフォルダなどが作成される。
ここまでが初期設定。
さて、Git使ってみるぞ。
Spring Boot/第五回 Spring Bootで Web (一覧表示)
Spring BootでWebの画面を作ってみる。
まずは顧客一覧画面から。
Contrller
ブラウザからのリクエストを受け取ってビジネスロジック呼んでブラウザにレスポンスを返すのがコントローラの役目。
コントローラは以下のようになる。
package tools.springsample.springsample05.web; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import tools.springsample.springsample05.domain.Customer; import tools.springsample.springsample05.service.CustomerService; @Controller // a @RequestMapping("customer") // b public class CustomerContrller { @Autowired private CustomerService customerService; @RequestMapping(method=RequestMethod.GET) // c public String list(Model model) { // d List<Customer> customers = customerService.findAll(); model.addAttribute("customers", customers); // e return "customer/list"; // f } }
a -> コントローラであることを記すアノテーション。
b -> コントローラがリクエストを受け取るURLを「customer」と定義する。
c -> URLの「customer」にGETメソッドが送られたときに実行するメソッドであることを記すアノテーション。
d -> 画面に値を渡すModelを引数に受け取る。
e -> ビジネスロジックの顧客一覧取得結果をモデルにセットする。
f -> 画面のテンプレート「customer/list.html」を呼び出す。
@Controllerアノテーションでコントローラであることを宣言して、@RequestMapping("customer")アノテーションで受け取るURLを定義して、@RequestMapping(method=RequestMethod.GET)アノテーションでHTTPメソッドの何を受け取って処理するメソッドかを宣言すると、そのメソッド(今回だとCustomerContrllerクラスのlistメソッド)が呼ばれる。
リクエスト処理(listメソッド)内では処理結果をモデルにセットして結果を表示するURLを戻り値で返せば、後のテンプレートへ処理結果を引き継げる。
今回は処理結果として、CustomerServiceで提供している顧客リスト取得メソッド(findAllメソッド)の結果をModelに「customers」という名前でセットした。
テンプレート
動的なページの作成に「Thymeleaf」(タイムリーフ)というテンプレートエンジンを使用する。
HTMLに「th:xxx」や「data-th:xxx」などのタグを入れるとThymeleafが適切な値に置き換えてくれる。
HTMLのテンプレートは下記のフォルダに置く。
src/main/resources/template
コントローラで「customer/list」と返しているので、対象のテンプレートは下記のファイルとして記述する。
src/main/resources/template/customer/list.html
まず普通のHTMLとして表示する画面を下記のようにする。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"/> <title>顧客一覧</title> <style type="text/css"> .list th { background-color: #cccccc; padding: 5px; } .list td { padding: 5px; } </style> </head> <body> <table class="list"> <tr> <th>ID</th> <th>氏名</th> <th>アドレス</th> </tr> <tr> <td>xx</td> <td>name</td> <td>address</td> </tr> </table> </body> </html>
このHTMLをブラウザで開くと下記の通り。
続いてHTMLにThymeleafで処理を書いて動的な結果を出力するように変える。
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org/"> <!-- a --> <head> <meta charset="UTF-8"/> <title>顧客一覧</title> <style type="text/css"> .list th { background-color: #cccccc; padding: 5px; } .list td { padding: 5px; } </style> </head> <body> <table class="list"> <tr> <th>ID</th> <th>氏名</th> <th>アドレス</th> </tr> <tr th:each="customer:${customers}"> <!-- b --> <td th:text="${customer.id}">xx</td> <!-- c --> <td th:text="${customer.name}">name</td> <!-- d --> <td th:text="${customer.email}">address</td> <!-- e --> </tr> </table> </body> </html>
a -> Thymeleafの名前空間を宣言。
b -> コントローラでModelにセットしたリスト「customers」を取得して「th:each」で件数分ループさせる。
ループ中の要素へアクセスするキーとして「customer」を指定する。
c -> 上で要素へのアクセスキー「customer」からIDを取得。
customersはCustomerクラスのリストなので、Customerクラスのフィールドidのゲッターメソッド結果をTDタグの値として当てはめる。
d -> Customerクラスのフィールドnameのゲッターメソッド結果をTDタグの値として当てはめる。
e -> Customerクラスのフィールドemailのゲッターメソッド結果をTDタグの値として当てはめる。
Thymeleafの処理を追記したこのHTMLをブラウザで開くと下記の通り。
追記前と変わらない。
つまりThymeleafの記述はブラウザには影響しない。
そのため、WebデザイナーとWebデベロッパーが分業して作業を進めることができる。
WebデザイナーがHTMLを書いて、そのあとWebデベロッパーがThymeleafの記述を追記する。
そのあとでもWebデザイナーはHTMLをブラウザで開いて表示内容を確認できるのでデザインを変更することもできる。
コンパイルして実行
以下のようにコンパイルする。
mvn package
続いて起動
mvn spring-boot:run
ブラウザで下記のアドレスにアクセスする。
http://localhost:8080/customer
するとデータベースの内容を読み込んで下記のように結果を表示する。
まとめ
コントローラとテンプレートを作って動的な画面を表示してみた。
次回はフォームを使って更新系の画面を試してみる。
■ 参考文献 ■
この記事で参考にしたのは「はじめての Spring Boot」です。
はじめてのSpring Boot―「Spring Framework」で簡単Javaアプリ開発 (I・O BOOKS)
- 作者: 槇俊明
- 出版社/メーカー: 工学社
- 発売日: 2014/11
- メディア: 単行本
- この商品を含むブログ (5件) を見る
Spring Boot/第四回 Spring Bootで REST(POST時のLocation設定)
RESTではPOSTでデータを新規作成した時は、そのデータにアクセスするURLをLocationヘッダーで返すのが一般的とのことで、それを試してみる。
新規にデータを登録して、オートインクリメントに設定したプライマリキー「ID」が「5」として登録できたら、以下のURLを返すということ。
api/customer/5
CustomerRestController の postCustomer メソッドを以下のように修正する。
@RequestMapping(method=RequestMethod.POST) public ResponseEntity<Customer> postCustomer(@RequestBody Customer customer, UriComponentsBuilder uriBuilder) { // a Customer createdCustomer = service.create(customer); // b // Locationで設定するURLを作成する URI location = uriBuilder.path("api/customer/{id}") // c .buildAndExpand(createdCustomer.getId()) // d .toUri(); // レスポンスのHTTPヘッダー HttpHeaders headers = new HttpHeaders(); headers.setLocation(location); // e // レスポンス情報を作成 return new ResponseEntity<>( createdCustomer, headers, HttpStatus.CREATED); // f }
a -> URI作成用クラスを引数で受け取る。
b -> 顧客情報の登録。
c -> プレースホルダ付の作りたいURLを指定する。
d -> プレースホルダに登録したIDを当てはめる。
e -> HTTPヘッダーにLocationのURLを設定。
f -> HTTPボディに createdCustomer の内容、
HTTPヘッダーに headers の内容、
HTTPステータスに CREATED を指定。
ビルドして Spring Boot を起動して curl で以下のように登録をする。
curl http://localhost:8080/api/customer -v -X POST -H "Content-Type: application/json" -d "{\"name\":\"name04\", \"email\":\"name04@test\"}"
実行結果は下記の通り。
# curl http://localhost:8080/api/customer -v -X POST -H "Content-Type: application/json" -d "{\"name\":\"name04\", \"email\":\"name04@test\"}" * Trying ::1... * Connected to localhost (::1) port 8080 (#0) > POST /api/customer HTTP/1.1 > User-Agent: curl/7.41.0 > Host: localhost:8080 > Accept: */* > Content-Type: application/json > Content-Length: 40 > * upload completely sent off: 40 out of 40 bytes < HTTP/1.1 201 Created < Server: Apache-Coyote/1.1 < Location: http://localhost:8080/api/customer/4 < Content-Type: application/json;charset=UTF-8 < Transfer-Encoding: chunked < Date: Fri, 03 Apr 2015 16:09:41 GMT < {"id":4,"name":"name04","email":"name04@test"}* Connection #0 to host localhost left intact
レスポンスヘッダーに今までなかった下記が追加されている。
< Location: http://localhost:8080/api/customer/4
うまく出来たようだ。
ちなみに、プレースホルダを解決する buildAndExpand メソッドには Map を引数に指定もできる。
// プレースホルダ解決用Map Map<String, Integer> map = new HashMap<>(); map.put("id", createdCustomer.getId()); // Locationで設定するURLを作成する URI location = uriBuilder.path("api/customer/{id}") // c // .buildAndExpand(createdCustomer.getId()) // d .buildAndExpand(map) // d .toUri();
プレースホルダの「{id}」を Map のキーにして値を当てはめてくれる。
■ 参考文献 ■
この記事で参考にしたのは「はじめての Spring Boot」です。
はじめてのSpring Boot―「Spring Framework」で簡単Javaアプリ開発 (I・O BOOKS)
- 作者: 槇俊明
- 出版社/メーカー: 工学社
- 発売日: 2014/11
- メディア: 単行本
- この商品を含むブログ (4件) を見る