谷本 心 in せろ部屋 このページをアンテナに追加 RSSフィード

2015-06-20

[][]Spring Boot + MOA その2 - Service Interfaceで呼び出しを明確にするよ?

前回のエントリーでは、Spring Bootで@RestControllerアノテーションを使ったサーバを作り、

RestTemplateでクライアントを作ることで、リモートサービス呼び出しを実装しました。

http://d.hatena.ne.jp/cero-t/20150618/1434645164


しかしこの方法は、クライアント/サーバ間に何の契約もなく、

サーバから呼び出し元のクライアントをたどることもできません、

言ってしまえば、リフレクションによるメソッド呼び出しをしているようなものでした。


リモートサービス呼び出しにインタフェースを導入する

この問題を解決するために、クライアント/サーバ間の契約として

インタフェースを挟むことを検討します。


サンプルは、以下に置いています。

https://github.com/cero-t/moa-sample/tree/master/moa-sample-2


それでは実装を順番に考えていきます。

まずは共通の「domain」モジュールに、Serviceのinterfaceを作成します。

https://github.com/cero-t/moa-sample/blob/master/moa-sample-2/student-domain-2/src/main/java/service/StudentService.java

public interface StudentService {
    Student get();

    int post(Student student);
}

これがリモートサービスで公開するメソッドの一覧になります。


続いて「service」モジュールのControllerを、このinterfaceの実装とします。

@RestController
public class StudentController implements StudentService {
    @RequestMapping(value = "/student", method = RequestMethod.GET)
    @Override
    public Student get() {
        Student student = new Student();
        student.id = 1;
        student.name = "TEST";
        student.score = 100;
        return student;
    }

    @RequestMapping(value = "/student", method = RequestMethod.POST)
    @Override
    public int post(Student student) {
        return 1;
    }
}

実装の内容は前回と変わらず、クラスをimplements StudentServiceとしただけです。


さらにサービスを呼び出すためのクラスも、同じくこのinterfaceの実装として作成します。

https://github.com/cero-t/moa-sample/blob/master/moa-sample-2/student-client-2/src/main/java/StudentServiceRemote.java

public class StudentServiceRemote implements StudentService {
    @Override
    public Student get() {
        ResponseEntity<Student> result = new RestTemplate().getForEntity("http://localhost:8080/student", Student.class);
        return result.getBody();
    }

    @Override
    public int post(Student student) {
        return new RestTemplate().postForObject("http://localhost:8080/student", student, Integer.class);
    }
}

前回はクライアント側のソースコード内にあったサービス呼び出しの処理を、ここに集約します。


このサービス呼び出し用クラスは、

「domain」モジュールに置いても「clientモジュールに置いても良いと思います。


リモートサービス呼び出しはクライアント側の責務だと考えれば「client」に置くことが正しいでしょうし、

「domain」に置くことで、リモートサービス呼び出しの共通ユーティリティとして利用できるので便利です。

今回、僕は「clientモジュールに置くことにしました。


最後に、このサービス呼び出しクラスを、クライアント側で利用します。

https://github.com/cero-t/moa-sample/blob/master/moa-sample-2/student-client-2/src/main/java/StudentClient.java

public class StudentClient {
    protected StudentService studentService = new StudentServiceRemote();

    public static void main(String[] args) {
        new StudentClient().execute();
    }

    public void execute() {
        Student student = studentService.get();
        System.out.println(student.id);
        System.out.println(student.name);
        System.out.println(student.score);

        Integer value = studentService.post(student);
        System.out.println(value);
    }
}

newしている辺りが若干微妙ですが、どうあれこれで、

クライアント/サーバ間で、同じインタフェースを共有することできました。


この方法を用いれば、Controller側のメソッドからOpen Call Hierarchyを行うことで

クライアント側のメソッド呼び出しをたどることができるようになり、形も綺麗になりました。


クライアント側もSpring Bootで。

おまけみたいなものですが、クライアント側もSpringベースにするため、少しだけ修正します。

サンプルは以下です。

https://github.com/cero-t/moa-sample/tree/master/moa-sample-3



リモートサービス呼び出しのクラスをSpringが管理するよう、@Componentアノテーションを追加します。

https://github.com/cero-t/moa-sample/blob/master/moa-sample-3/student-client-3/src/main/java/application/remote/StudentServiceRemote.java

@Component
public class StudentServiceRemote implements StudentService {
    @Override
    public Student get() {
        ResponseEntity<Student> result = new RestTemplate().getForEntity("http://localhost:8080/student", Student.class);
        return result.getBody();
    }

    @Override
    public int post(Student student) {
        return new RestTemplate().postForObject("http://localhost:8080/student", student, Integer.class);
    }
}

相変わらず実装は変わりません。URLの直書き感もそのままです。


そしてクライアント側では、上で作ったクラスがDIされるよう@Autowiredアノテーションを追加します。

https://github.com/cero-t/moa-sample/blob/master/moa-sample-3/student-client-3/src/main/java/application/client/StudentClient.java

public class StudentClient {
    @Autowired
    StudentService studentService;

    public void execute() {
        Student student = studentService.get();
        System.out.println(student.id);
        System.out.println(student.name);
        System.out.println(student.score);

        Integer value = studentService.post(student);
        System.out.println(value);
    }
}

これでリモートサービス呼び出し部分が、綺麗に隠れました。


ここまでのまとめ

今回、クライアント/サーバ間の契約として、Serviceのinterfaceを利用しました。

このような形にしておけば、サーバクライアントの呼び出し関係が明確になり、

IDEのOpen Call Hierarchy機能を利用して、呼び出しをたどることもできるようになります。


また(別に、interfaceを導入したためというわけではないですが)

JUnitでのテスト時に、リモートサービス呼び出し部分をモック化しやすくなりました。


これがリモートサービス呼び出しの最もベーシックな形になると思います。

それなりに大きな規模の案件で、たとえ十分に管理ができなくなってきたとしても、

この形さえ守られていれば、後からサーバクライアントの呼び出し関係を整理することができます。


ただ、サービス呼び出し用のServiceRemoteクラスを毎度自前で実装するのは面倒です。

次回はこの辺りの改善を考えてみます。

2015-06-18

[][]Spring Boot + MOA その1 - Spring BootでMicroserviceのクライアント/サーバを実装している途中の話

最近のアーキテクチャ界隈の話題と言えば、Microservice。

MSAと略すかMOAと略すか、みたいな議論もあるようですが、

CEROMETAL的にはMOAを一押しせざるを得ない状況です。

MOAMETAL - google画像検索


いいとして。


システムをMicroservice的に設計すると、小さな粒度のWebサービスができあがります。

そしてこのWebサービス間の通信をするためには、

HTTPなりAMQPなりのプロトコルを使ったリモートサービス呼び出しが必要となります。


SOA的に考えれば、ESBやCamelのようなメッセージングバスを利用するという仕組みもアリですが、

もう少しライトに、簡単なHTTPコールで呼び出しを済ませたいと思うことも多いでしょう。


今回はその辺りをゼロベースで考えて「僕が考える最強のリモートサービス呼び出し」を見つけるべく、

実装しながら試行錯誤したので、その辺りの過程を記録しておきます。


なお、サンプルソースは全部githubに上げています。

https://github.com/cero-t/moa-sample


まずはシンプルにRestTemplateと@RestControllerで実装。

まずはシンプルに、サーバ側はSpring Bootの@RestControllerを使ったサービス実装とし、

クライアント側はRestTemplateを使うことにします。


まずサーバ側から。こんなStudentクラスを作ります。

https://github.com/cero-t/moa-sample/blob/master/moa-sample-1/student-domain-1/src/main/java/domain/Student.java

public class Student {
    public Integer id;
    public String name;
    public Integer score;
}

見ての通り、public field厨です、サーセンww


次に、Studentを使ったControllerを実装します。

https://github.com/cero-t/moa-sample/blob/master/moa-sample-1/student-service-1/src/main/java/application/controller/StudentController.java

@RestController
public class StudentController {
    @RequestMapping(value = "/student", method = RequestMethod.GET)
    public Student get() {
        Student student = new Student();
        student.id = 1;
        student.name = "TEST";
        student.score = 100;
        return student;
    }

    @RequestMapping(value = "/student", method = RequestMethod.POST)
    public int post(Student student) {
        return 1;
    }
}

かなり適当な実装ですがいいとして。


そしてクライアント側の実装です。

https://github.com/cero-t/moa-sample/blob/master/moa-sample-1/student-client-1/src/main/java/StudentClient.java

public class StudentClient {
    public static void main(String[] args) {
        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<Student> result = restTemplate.getForEntity("http://localhost:8080/student", Student.class);
        Student student = result.getBody();
        System.out.println(student.id);
        System.out.println(student.name);
        System.out.println(student.score);

        Integer value = restTemplate.postForObject("http://localhost:8080/student", student, Integer.class);
        System.out.println(value);
    }
}

URLlocalhost:8080の直書きとか、もう、しびれる感じの実装ですね!


これでひとまず、Webサービスの呼び出しができあがります。

ほら、簡単でしょう?


モジュールは「サービス実装」と「サービスインタフェース(入出力)」で分けるべき。

ところで、僕はWebサービスの「サーバ側」を実装をする際に、

2つのモジュールに分割するようにしています。

- domain : 引数戻り値のクラスを集めたモジュール

- service : サービス実装本体のモジュール


上の例で言えば、

「Student」クラスは「domain」モジュールに含まれて、

「StudentController」クラスは「service」モジュールに含まれます。


そうすることで「StudentClient」クラスは「domain」モジュールのみに依存し、

「service」モジュールには依存しないような構成を取ることができます。

それで「clientモジュールのフットプリント的にもずいぶん違ってくるはずです。


ここまでのまとめ

すごく簡単な形ですが、シンプルなWebサービス呼び出しができました。

また、サーバ側は「domain」「service」に分けることで、

インタフェース側と実装側に分離する形を取りました。


ただこの実装では、

あまりにもHTTPによる通信が実ソースに表れすぎですし、

サーバ側のAPIは「自分がどこから呼ばれるのか」を知るすべがありません。


次回はこの辺りの課題を解決すべく、

Service interfaceを導入する方向を検討してみます。


Stay metal, see you!