
前回(No.11の記事)は、PostgreSQLのテーブルへアクセスする、簡単なプログラムを作成しました。
まぁ、一応動いたけれど、まだまだです。
もっと進めて、Webアプリケーションっぽくしていく必要があります。
アプリケーション構造を再確認
改めて、どんな感じにすれば良いのか、構造を整理しておきます。

① Angular App が、Controllerの「カテゴリ一覧取得」処理を呼び、
①’で、カテゴリ一覧を取得する。
②Controllerが、Serviceの「カテゴリ一覧取得」処理を呼び、
②’で、カテゴリ一覧を取得する。
③Serviceが、RepositoryEntityの「カテゴリ一覧検索」処理を呼び、
③’で、カテゴリ一覧を取得する。
という流れ。
ControllerとServiceって、この程度のプログラムだと「分ける必要ないのでは?」という感じもするけれど、
Controllerは、「画面と結びついた比較的単純なやり取り」を担当。
Serviceは、「トランザクション制御やちょっと複雑な業務処理」を担当。
という、役割分担かな。。。
少しずつ作っていくほうが気分的には楽なので、まずはController単体バージョンを作ります。
パッケージ構成を決める
今後いろいろ作っていくことを考えると、予めパッケージ構成を考えておいたほうが良さそう。
調べたら、いろんな人が、いろんなことを言っていて、「これが正解だ!」とか「なんか、しっくりくる」というのが微妙に見当たらないのだけれど、検討の結果、「(画面)機能系は “~app” 配下 」、「業務・DB系は “~domain” 配下」とする、こんな感じが良さそうに思ったので、
com.chankazu.tts
├ app
├ controller
CategoryController.java
├ dto
CategoryDto.java
├ domain
├ entity
CategoryEntity.java
├ repository
CategoryRepository.java
├ services
CategoryService.java
とりあえず上記の形で進めることにしました(必要に応じて調整ということで)。
規模が大きいシステムでは、「~.app.機能別」とかにしたほうが良さそうだけれど(区分けが分かりやすい、分業しやすい)、今回は小さなアプリなので、このパターンで良いでしょう。
あと、「~.app.dto」配下の CategoryDto.java は、いるのかどうかは、まだよくわからない。
次回、明らかにしようと思います。
コントローラーを作る
画面からの要求により、カテゴリ・テーブルの一覧データを取得してくる処理を作ります。
まずはこんな感じで。
package com.chankazu.tts.app.controller;
import java.util.List;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import com.chankazu.tts.domain.entity.CategoryEntity;
import com.chankazu.tts.domain.repository.CategoryRepository;
@RestController
class CategoryController {
private final CategoryRepository repository;
CategoryController(CategoryRepository repository) {
this.repository = repository;
}
// Aggregate root
// tag::get-aggregate-root[]
@GetMapping("/categories")
List<CategoryEntity> all() {
return repository.findAllByOrderByIdAsc();
}
// end::get-aggregate-root[]
// Single item
@GetMapping("/categories/{id}")
CategoryEntity one(@PathVariable Long id) {
return repository.findById(id);
//.orElseThrow(() -> new categoryNotFoundException(id));
}
}
ブラウザから「http://localhost:8080/categories」と入れられたら、カテゴリ・テーブルの内容全てを返し、「http://localhost:8080/categories/2」等、IDを指定されていたら、当該IDの内容のみを返します。
※orElseThrowはうまく通らなかった、、、追々で
Entity と Repository
EntityとRepositoryも一応、載せておきます。
package com.chankazu.tts.domain.entity;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "category")
public class CategoryEntity {
@Id
private Long id;
private String name;
protected CategoryEntity() {}
public CategoryEntity(Long id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return String.format(
"Category[id=%d, name='%s']",id, name);
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.chankazu.tts.domain.repository;
import java.util.List;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import com.chankazu.tts.domain.entity.CategoryEntity;
@Repository
public interface CategoryRepository extends CrudRepository<CategoryEntity, Integer> {
List<CategoryEntity> findAllByOrderByIdAsc();
CategoryEntity findById(Long id);
}
名前を少し変えたり「~domain」配下に移動したりしたけれど、基本的には前回と同じ。
ただ、「findAll」メソッドは、そのままだと素っ気ないSQLになるので、「findAllByOrderByIdAsc」として、”全部検索、IDの昇順にしてください” と、JPAにお願いしました。
実行時に生成されるSQLは以下のようになります(コンソール・ログから)。
select
categoryen0_.id as id1_0_,
categoryen0_.name as name2_0_
from
category categoryen0_
order by
categoryen0_.id asc
たしかに、ルールに沿って特定の名前にしただけで、それなりのSQLが出来上がるというのは、便利だけれど、どうなんだろう?、やり過ぎでは?。
「便利だけど不自由」Vs「不便だけど自由」みたいな感じ?
以外と世の中、便利に出来ることだけでは済まなくて、そうすると途端に続けて使うことが難しくなってしまったりするので、最初から多少難易度が高くても、自由なほうが良い気がしますが。。。
※findメソッド名のサンプルはこのあたりにありますね。
実行してみる
Eclipseで、Gradle・bootRunします。

すると最初は、エラーになる、、、(なぜ?)

***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in com.chankazu.tts.app.controller.CategoryController required a bean of type 'com.chankazu.tts.domain.repository.CategoryRepository' that could not be found.
Action:
Consider defining a bean of type 'com.chankazu.tts.domain.repository.CategoryRepository' in your configuration.
> Task :bootRun FAILED
「Parameter 0 of constructor in ~ required a bean of type ~ that could not be found.」
う~ん、、、「CategoryRepository」が見つけられない、と言われている。
「いや、あるのに?」ということで、いろいろ調べて、「@ComponentScan(com.chankazu.tts.domain)」とか定義してみたりしたけれど、どうもなんか、すっきりしない。
「com.chankazu.tts.app」にはControllerがあり、「com.chankazu.tts.domain」には Entity と Repositoryがあるのだけれど、appとdomain とに分けず、「~app」配下に全て置くとうまくいく。
場所の問題というか、生成ルールの問題というか。
けど、この程度のアプリで、「@ComponentScan(~)」などという、ある意味面倒な定義を、ソースにわざわざ追加しないと動かないのはおそらく、、違うよね?、こんな指定しなくて済む方法あるよね?、と悩むこと数時間。
そういえば、Applicationクラス、「~app」配下にしていました。
com.chankazu.tts
├ app
├ controller
├ dto
Application.java <<ここにあった
├ domain
├ entity
├ repository
├ services
package com.chankazu.tts.app;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
つまり、ここにあるから、「~domain」側の生成まで面倒見てくれようとしないのでは?、、ということで「~tts」配下に移動。
com.chankazu.tts
├ app
├ controller
├ dto
├ domain
├ entity
├ repository
├ services
Application.java <<ここに移動
(ApplicationTests.java も移動)
この状態で実行すると、

うん、動いてる(ダメだと、ここですぐ停止)。
そして、ブラウザからのリクエストに対しては、
・「http://localhost:8080/categories」の場合

・「http://localhost:8080/categories/2」の場合

いい感じで返って来ています。
ちょっとハマりましたが、なんとかなりました。とりあえず満足。
次回は サービス を作ってみよう
パッケージ構成を概ね決めて、Controllerをとりあえず作った&動いたので、次回は Service を作ります。
現状だと、Controller でやっていた処理を Service にもっていくだけで、たいしたことはしないと思うけれど。
今回やらなかった「(Entity→画面・JSON向け)のオブジェクトの詰め替え」的なことは、いる・するのか?/いならいのか?
com.chankazu.tts.app.dto.CategoryDto.java はいるのか?/いらないのか?
このあたりを確認・整理したいと思います。
コメント
[…] 前回(No.12の記事)は、コントローラーを作成し、ブラウザからのリクエストに対して、テーブル検索をして、値を返せるようにしました。 […]