
前回(No.12の記事)は、コントローラーを作成し、ブラウザからのリクエストに対して、テーブル検索をして、値を返せるようにしました。
一応、コントローラーとレポジトリの連携は出来ました。
ただ、役割的には、コントローラーって「機能系、画面系」の処理をまとめるもので、サービスが「DBとのやりとり、業務処理」をやるところなんだろうなぁ、、、と思っています。
なので今回は、サービスを作って、検索はそこからするようにしてみます。
アプリケーション構造を再確認
サービス(Service)の位置づけを、改めて確認しておきます。

今回作るServiceは、以下のような流れ。
①Controllerから呼び出され、②検索処理をして、③Controllerに結果を返す。
では作っていきましょう♪。
サービスを作る
Serviceは、Controllerから「カテゴリを全検索して、一覧をくれ」とお願いされる想定なので、なかでカテゴリ検索を実行する、以下のようなクラスを作成しました。
・interface「CategoryService」
package com.chankazu.tts.domain.service;
import java.util.List;
import org.springframework.stereotype.Service;
import com.chankazu.tts.domain.entity.CategoryEntity;
@Service
public interface CategoryService {
List<CategoryEntity> findAll();
CategoryEntity findById(Long id);
}
・implements「CategoryServiceImpl」
package com.chankazu.tts.domain.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.chankazu.tts.domain.entity.CategoryEntity;
import com.chankazu.tts.domain.repository.CategoryRepository;
@Service
public class CategoryServiceImpl implements CategoryService {
@Autowired
CategoryRepository categoryRepository;
public List<CategoryEntity> findAll() {
List<CategoryEntity> categories = categoryRepository.findAllByOrderByIdAsc();
if (categories == null) {
// エラー処理
は追々
}
return categories;
}
public CategoryEntity findById(Long id) {
CategoryEntity category = categoryRepository.findById(id);
if (category == null) {
// エラー処理
は追々
}
return category;
}
}
いろいろ参考にしながら、とりあえず作ってみましたが、いくつか気になることがあったので、ちょっとまとめておきます。
interfaceとimplements
CategoryService(interface)とCategoryServiceImpl(implements)に分けました。
宣言(interface)と実装(implements)を分けると「こんな良いことが」「あんな良いことが」「ありますよ~」という記事をちらちら見かけますが、この程度のアプリでは、それほどメリットはみえませんね。
実はSpringがそうしてほしそうですが、今はそこを深追いするのもしんどいので、当面はお約束に従うことにします。
@Service?、@Autowired?
薄々気付いてはいました。「最近のプログラムは、”new”しないな~」と。
旧い人間なので、しっかり・がっちり ”new” して、あちこち繋ぎとめておいたほうが落ち着くのですが、「もうこの際、そこはフレームワークに任せましょう」というのが今の流れなのかな?。
@Serviceとか @Componentをつけて、「こいつを管理してください」とお願いし、@Autowired で「いいタイミングでnewしといてください」とお願いする感じですね。
雑な理解では後でまた悩むことになるかもしれませんが、悩む時が来ないと結局、深く納得はできないわけなので、いったん良しとします。
コントローラーを見直す
Serviceの実装にあわせて、Controllerも修正を行いました。
ポイントがいくつかあります。
package com.chankazu.tts.app.controller;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
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.app.dto.CategoryDto;
import com.chankazu.tts.domain.entity.CategoryEntity;
import com.chankazu.tts.domain.service.CategoryService;
@RestController
class CategoryController {
@Autowired
CategoryService categoryService;
// Aggregate root
// tag::get-aggregate-root[]
@GetMapping("/categories")
List<CategoryDto> findAll() {
List<CategoryEntity> entityList = categoryService.findAll();
List<CategoryDto> dtoList = new ArrayList<CategoryDto>();
for(CategoryEntity entity : entityList){
dtoList.add(new CategoryDto(entity));
}
return dtoList;
}
// end::get-aggregate-root[]
// Single item
@GetMapping("/categories/{id}")
CategoryDto findById(@PathVariable Long id) {
CategoryEntity entity = categoryService.findById(id);
CategoryDto dto = new CategoryDto(entity);
return dto;
}
}
CategoryServiceを@Autowired
前回のCategoryControllerは、CategoryRepositoryをもらってくる形で初期化して、その後、CategoryRepository内のメソッドを呼び出すようにしましたが、今回は、CategoryServiceを@Autowired指定して、初期化をお願いする形にしました。まぁ、こっちのほうがシンプル。
DTOへの詰め替え
Serviceのメソッドは、Entity(のリスト)をそのまま返してきます。
Controllerは、それを受け取って、画面側(現状想定しているのは、Angular)に戻す予定なのだけれど、Controllerから画面に情報が遡る時は、Entityの形がそのまま戻っていくのはいまいちだろうと思う(DB上の形が画面までいってしまう)。
なので、Controllerは、サービスから受け取ったEntity(のリスト)を、さらに画面に受け渡す形(dto)に変える必要があるのでは?、と思って、Listを回してEntity→dtoの詰め替えをしています。
けど、なんかもやもやする。。。
①機能・画面系dtoへの詰め替え処理の場所って、コントローラーで良い?
※これは、Controller側で良さそうです
②こんな、( for(CategoryEntity entity : entityList) で )ぐるぐる回す(古めかしい)やり方しかない?
※Mapperとかが使えるかも?、いつかやってみたい
③そもそも、domein.entity.CategoryEntity と、app.dto.CategoryDtoって現状違いがほぼない。
package com.chankazu.tts.app.dto;
import com.chankazu.tts.domain.entity.CategoryEntity;
public class CategoryDto {
private Long id;
private String name;
public CategoryDto(Long id, String name) {
this.id = id;
this.name = name;
}
public CategoryDto(CategoryEntity entity) {
this.id = entity.getId();
this.name = entity.getName();
}
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.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;
}
}
※微妙~に違いますけどね、分けるほどでもない気がする
このあたり、より「エレガントな」作り方の探求は、継続課題としていこうと思います。
実行してみる
Eclipseから、bootRunしてみます。

「http://localhost:8080/categories」を行うと、

中身は変ったのですが、前回と同様の結果です。問題ないようです。。
次回は画面(Angular)を作る
簡単な、検索指示と検索結果表示を、Angularベースで作ってみます。
あと、そろそろ、コードは、gitに公開したほうが良いかもしれない。
コードの貼り付けが多くなってきた(今後さらに増えると予想)ので、記事に貼るのは伝えたい部分だけにして「後はgitを見てね」のほうが、必要以上に記事が大きくならず、良さそうです。
コメント