Goalist Developers Blog

Let's { Dart } before we { Flutter }



The above program was written in Dart and it shouldn't come to you as a surprise if you are from Java or C programming background. (above lines are editable so go ahead and replace my name with yours and press play button ▶️)

So, what is Dart???
An overview of Dart on Ecma website reads as

Dart is a class-based, single-inheritance, pure object-oriented programming language. Dart is optionally typed and supports reified generics.

If that thing was steaming pile of mumbo-jumbo to you then allow me to explain in my subsequent blog named "Getting Started with Dart".

The greatest innovations are the ones we take for granted, like light bulbs, refrigeration, and penicillin.

However; in a world where the miraculous very quickly becomes common-place, how can a company, especially one as big as Google, maintain a spirit of innovation year after year? 🤔

While you are thinking about it... eureka!! there is already something new in the market and you gotta learn it, because...
Once a new technology rolls over you, if you're not part of the steamroller, you're part of the road.
With no surprise, Google this year came with the complete set of frameworks for all your needs, with its own programming language {Dart}.

1) Web-Applications (AngularDart)

AngularDart is a web app framework that focuses on productivity, performance, and stability.

2) Android and iOS Applications (Flutter)

Flutter is Google’s mobile app SDK for crafting high-quality native interfaces on iOS and Android in record time using only single code base.


So let's have a tour of these things together...

f:id:vivek081166:20180723142405p:plain


See you in the subsequent blog post with

"Getting Started with Dart"

till then happy learning 😊

VAPIDを用いたデスクトップ通知のAngular6とJavaによる実装

こんにちは。
新卒の増田です。

今回、クライアント(フロントエンド)にAngular6、サーバー(バックエンド)にPlayFramework(Java)を使ってデスクトップ通知を実装しようとした際、あまり参考になる資料がなかったのでここに残しておきます。

VAPIDやPush通知に関する細かい仕組みについては他のサイト(ここなど)を参照していただいて、で、実際に何を書けば良いのかにだけ焦点を当てて紹介していきます。

僕はエンジニア新生児なので、初めから書いていきます。
何もないところからデスクトップ通知ができるように。
もう本当に初めの方から。
ただ、sbtやAngular CLIなどのツールはこっそり入っているという前提です。

使っているもの

  • PlayFramework 2.6
  • Java 1.8
  • Eclipse 4.7
  • Angular6
  • TypeScript
  • WebStorm

ディレクトリとプロジェクトの作成

まず、適当なところにフォルダを作ります。名前も適当に。

$ mkdir notification
$ cd notification

そしてそこにクラアイアント(Angular)とサーバー(PlayFramework)、それぞれのプロジェクトを作成します。コマンドで。
まずはサーバー側。

notification$ sbt new playframework/play-java-seed.g8

name [play-java-seed]: backend
organization [com.example]: 
scala_version [2.12.6]: 
play_version [2.6.16]: 
sbt_version [1.1.6]: 

色々聞かれますが、とりあえず名前だけbackendと付けてあげてあとはEnterキーです。
もちろん名前もなくて良いです。お好みで。

次はクライアント側。

notification$ ng new frontend

こちらも名前はお好みで。
さて、これでとりあえずはディレクトリができました。いえい。

サーバー側の準備

サーバー側から触っていくことにします。
というわけで何も考えず

notification$ cd backend

そして今回はEclipseを使っていくので、project/plugins.sbtに

addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "5.2.4")

を書き加え、ついでに今回必要なものは全てありがたいライブラリを使わせていただくので、build.sbtにあれこれ追記します。

libraryDependencies ++= Seq(
  filters,
  "org.bouncycastle" % "bcprov-jdk15on" % "1.54",
  "nl.martijndwars" % "web-push" % "3.1.0",
  "commons-io" % "commons-io" % "2.4"
)

はい。
ここで、plugins.sbtやbuild.sbtに追加したものをEclipseに適用するために

backend$ sbt eclipse

をしておきます。
次は、サーバーとクライアントで通信をするのですが、このままだとクライアントからサーバーにリクエストを送ってもCORSで弾かれてしまいます。CORSについては割愛します。僕もよく知りません。

とりあえず、application.confに

play.filters {
  ## CORS filter configuration
  # https://www.playframework.com/documentation/latest/CorsFilter
  # ~~~~~
  # CORS is a protocol that allows web applications to make requests from the browser
  # across different domains.
  # NOTE: You MUST apply the CORS configuration before the CSRF filter, as CSRF has
  # dependencies on CORS settings.
  enabled += "play.filters.cors.CORSFilter"
  cors {
    # Filter paths by a whitelist of path prefixes
    pathPrefixes = ["/"]

    # The allowed origins. If null, all origins are allowed.
    allowedOrigins = null

    # The allowed HTTP methods. If null, all methods are allowed
    allowedHttpMethods = null
    allowedHttpHeaders = null

    #preflightMaxAge = 3 days
  }
}

と書き加えた上で(コメント部分はもちろんなくても)、appの下にFilters.javaというクラスを作成します。Eclipseで普通にクラスを作成するとデフォルトパッケージの下に来ますが気にしません。
で、Filters.javaに

import javax.inject.Inject;

import play.filters.cors.CORSFilter;
import play.http.DefaultHttpFilters;
import play.mvc.EssentialFilter;

public class Filters extends DefaultHttpFilters {

    CORSFilter corsFilter;

    @Inject
    public Filters(CORSFilter corsFilter) {
        super(corsFilter);
        this.corsFilter = corsFilter;
       }

    public EssentialFilter[] filters() {
        return new EssentialFilter[] { corsFilter.asJava() };
    }
}

というのを貼り付けましょう。
PlayFrameworkのHPからサンプルをダウンロードすると初めからあるような気がするので、
そこからコピペしても良いと思います。これでCORSで弾かれなくなりました。幸せ。

ここまででサーバー側の設定はおしまいです。

クライアント側の準備

次はクライアント側の準備に移ります。
とりあえずfrontendディレクトリに移って、まずはこのプロジェクトをPWA化しましょう。
デスクトップ通知にはサービスワーカーが欠かせないらしいので、そのためです。
サービスワーカーが何であるのかについては世に遍在している説明を発見していただければ。

frontend$ ng add @angular/pwa —project frontend
frontend$ npm i @angular/service-worker --save -D

クライアント側の設定はこれでおしまいです。Angular CLIさんが必要な記述も全部してくれていますので。ちなみに ここなどを参考にしています。 参考ページではindex.htmlとmanifest.jsonを修正していますが、必要ありませんでした。

というわけで、これでサービスワーカーを使えるようになりました。

ここまでで必要な準備が終わったのでやっと本題に入れます。
デスクトップ通知の実装へ。

サーバー側でECDSAによる鍵ペアを生成

鍵ペアの生成には初めに準備しておいたライブラリを使わせていただきます。
ECDSAというのがどういうものなのかよく知りません。
知りませんが、それでも使えるというのが大事ということで。

鍵ペアですが、実は毎回作成する必要はなく、アルゴリズムに従って生成された鍵ペアさえあれば良いので、一度作ったものをそのままペタッ。

とりあえず一度生成すべく、Eclipseでapp下に何か適当なメインクラスを作成します。
本当はパッケージなど分けた方が良いのでしょうが、そういう細かいことは必要に迫られたときに。
例えば表示するだけならこのように、VapidKey.javaのようなクラスを作成します。

import java.security.Security;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import nl.martijndwars.webpush.cli.commands.GenerateKeyCommand;
import nl.martijndwars.webpush.cli.handlers.GenerateKeyHandler;

public class VapidKey {

    public static void main(String[] args) {
        try {
            new VapidKey().generateKeys();
        } catch (Throwable th) {
            th.printStackTrace();
        }

    }

    public void generateKeys() throws Throwable {
        Security.addProvider(new BouncyCastleProvider());
        GenerateKeyHandler gh = new GenerateKeyHandler(new GenerateKeyCommand());
        gh.run();
    }
}

全部mainに入れれば良いじゃんと思いつつ。
ただ、何もせずにこれを実行すると、たぶん「メインクラスが見つかりません」などと言われます。
なので、

backend$ sbt compile

をして、プロジェクトをインポートし直し、ここにあるように、ビルドパスを設定します。

  • Eclipse上でプロジェクトを右クリック > Build Path > Configure Build Path...
  • LibraryタブからAdd Class Folderを選択
  • 開いたダイアログでtarget/scala-2.12/classesにチェックを入れてOK

そしたら実行できるようになるはずです。
実行できました。できたらコンソールに

PublicKey:
BP198Ghlpaac41UMKrRvYyNt56tt7JCcgusX1JSh1e3W-kApskF2BLqCNi0c2GBjPx5BflPezwvi0OwVsaGTclI=
PrivateKey:
Kzh4c_SbGLXoybns7pCiRdvhx9b0m_H5AEyD8DD9Bvw=

のように出力されるのではないかと。

繰り返しになりますが、アルゴリズムに従って生成された鍵ペアさえあれば良いので、上記のものをそのまま利用してもらっても大丈夫なはずです(なのでこのクラスを作らずに上の鍵を使うのでも良いような)。セキュリティ的にどうなのかというのはひとまずおいといて。

生成したサーバー鍵をクライアント側に送信

クライアントからリクエストを送って、そのレスポンスでサーバー鍵を渡します。
HomeController.javaに

import play.libs.Json;

public Result vapidKey() {
     // さっきの鍵をそのままペタッ
     String serverKey = "BP198Ghlpaac41UMKrRvYyNt56tt7JCcgusX1JSh1e3W-kApskF2BLqCNi0c2GBjPx5BflPezwvi0OwVsaGTclI=";
     // サーバーキーを返すだけ
     return ok(Json.toJson(serverKey);
}

というように追記した上で、routesに

GET /vapidKey        controllers.HomeController.vapidKey

としておくことで、この/vapidKeyにリクエストが送られたときにサーバーの公開鍵を返すようになります。
普通です。

プッシュサーバーにサーバー鍵を登録

subscriptionからエンドポイントやクライアントの公開鍵等を取得

取得した諸々の情報をサーバー側へ送信

クライアントでPush通知を受け取った際にデスクトップ通知として表示

4つまとめてやります。

app.component.tsを

import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { SwPush } from '@angular/service-worker';

@Componetn(中略)

export class AppComponent implements OnInit {

    constructor(private swPush: SwPush, private http: HttpClient) {

        // Push通知が送られて来たときに受け取るやつ
        this.swPush.messages.subscribe(
            (message) => {

                const options = {
                    body:  message['message'],
                    // trueにするとユーザーから消されるるまで表示し続けられます。
                    requireInteraction: true,
                    // tag名が異なると異なる通知として出され(下にずらっと並ぶ)、
                    // tag名が同じものはそれ以前からある通知に上書きされるようです。
                    tag: 'notification_tag',
                    // これが何かは忘れてしまいました
                    renotify: false,
                };

                // どうしてかよく分かりませんが、Notificationをnewすると表示されるみたいです。
                const n = new Notification(message['title'], options);

            });
    }

    ngOnInit() {
        this.setDesktopNotification();
    }

    setDesktopNotification(): void {
        // サーバー公開鍵を受け取るべくリクエストを送信
        this.http.get('http://localhost:9000/vapidKey')
            .subscribe(
                // レスポンスに鍵が入っているはずなので、それをserverPublicKeyにセット
                (res) => {
                    this.swPush.requestSubscription({
                        serverPublicKey: res.toString()
                    }).then(
                        (pushSubscription: PushSubscription) => {

                            // 必要なものは全てpushSubscriptionに入っているので、アクセスすると取れます。
                            // endpointとp256dhとauthを取得
                            const endpoint = pushSubscription.endpoint;
                            const p256dh: string = btoa(String.fromCharCode.apply(null, new Uint8Array(pushSubscription.getKey('p256dh'))));
                            const auth: string = btoa(String.fromCharCode.apply(null, new Uint8Array(pushSubscription.getKey('auth'))));

                            const body = {
                                       endpoint: endpoint,
                                       p256dh: p256dh,
                                       auth: auth
                            };

                            // 送り先は/webPushという場所にしました。
                            this.http.post('http://localhost:9000/webPush', body).subscribe(
                                (res) => {
                                    // console.log('[App] Add subscriber request answer', res);
                                 },
                                 (err) => {
                                     console.log(err);
                                 });
                        }).catch(
                            err => console.log(err)
                        );
                },
                (err) => {
                    console.log(err);
                });
    }

}

として、
あとComponentでHttpClientを使うために、app.module.tsにちろっと追記しておきます。

import { HttpClientModule } from '@angular/common/http';

@NgModule({
    (中略)
    imports: [
        ...
        HttpClientModule,
        ...
    ],
    ()
})

サーバー側からメッセージをエンドポイントへと送ると、
プッシュサーバーがクライアント側にPush通知を送ってくれます。
あとはそれを受け取ってあれこれするとデスクトップ通知ができるのです。きっと。ここでそのプッシュサーバーからの通知を受け取る役目を負っているのが、constructorの中のthis.swPush.messages.subscribe()です。

ちなみに、Notificationのオプションはここに載っているものが使えるのではないかと勝手に思っています。「Instance properties」のところですね。

一つ引っかかったところがありまして、pushSubscriptionに入っているならそれをそのまま返せば良いじゃないかと初めはなったのですが、そのまま返すとなぜか文字が欠けてしまったりします(この理由がよく分かってません)。
なので、文字列に変換してから必要な分だけを渡しています。

というわけで、送信できるようになりました。

クライアント側から送られてくるリクエストを受け付ける窓口作成

当然のことながら、上記でクライアントから送った情報を受け取る窓口が必要になります。
さっきと同じようにして、HomeController.javaに

import play.mvc.Http.Request;

public Result webPush() {

        Request req = request();

        // ここの受け取り方はクライアントからどう送るかによって色々です。たぶん。
        JsonNode jn = req.body().asJson();
        String endpoint = jn.get("endpoint").asText();
        String p256dh = jn.get("p256dh").asText();
        String auth = jn.get("auth").asText();

        System.out.println("endpoint : " + endpoint);
        System.out.println("p256dh : " + p256dh);
        System.out.println("auth : " + auth);

        return ok(Json.toJson("OK!"));
}

と追記し、そしてroutesに、

POST /webPush        controllers.HomeController.webPush

というような感じで追記しておくと、クライアントから送られて来た情報がコンソールに出力されると思います。

ここまで来たら次はサーバーとクライアントで通信させる必要があるので、サーバーとクライアントをそれぞれbuildします。
まずはサーバー側を

backend$ sbt run

おしまい。

次はクライアント側

frontend$ ng build --prod
frontend$ cd dist/frontend
frontend$ http-server

突然出て来たhttp-serverというのは、ありがたい記事によると

このhttp-serverをインストールしておくと、任意のディレクトリでhttp-serverというコマンドを実行するだけでそのディレクトリをドキュメントルートにしたウェブサーバーが起動します。

というものらしいです。とりあえず

$ npm install -g http-server

をしておけば良いのではないでしょうか。あって困るものではないでしょうから。
そしてあとはlocalhost:8080をブラウザで開くと、通信が行われて、サーバーのコンソールに

endpoint : https://fcm.googleapis.com/fcm/send/eit1VHpri4g:APA91bFWD6MZw-ulAbKRlIfSyqUvRYGqcPfUFmxqkj4bWmUzUaBB-6OZyUmvy9Cob6WYHJH_JpWeU-C_nPsDO7yiMEqPXg6DXD3KXKiS85BgbKgQxcuD15_LK3s09yxeiqKcCrx6IUasFfBq0oQ6nt5b0SxiPXX4ug
p256dh : BOeKRtnhXWm+dSVAHP3ws7EE6cYUi83Lb6ny7+IweHniUSywQAc9BzZmVtVEyY5SKGxR4QGG6PAWNzlr5LhadA8=
auth : 4rIMQM5HTe04kclcRU2Rhw==

というように出力されるのではないかと(上手くいかなかったらリロードなどを)。
ちなみに、サーバーの鍵ペアはここに載せてあるものでも使えますが、エンドポイント等はここにあるものをコピペしてもたぶん送信できませんのでお気をつけて。

大事なことを一つ。あとでPush通知を飛ばしたときに受け取らないといけないので、フロント側は動かしたままに。

エンドポイントへとメッセージを送信

これも先のライブラリを使わせていただくと、 とっても簡単に送信できてしまいます。便利。
適当にapp下にWebPush.java(名前は適当に)をこんな感じで作成し、

import java.security.Security;

import org.apache.http.HttpResponse;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import com.google.gson.JsonObject;

import nl.martijndwars.webpush.Notification;
import nl.martijndwars.webpush.PushService;
import nl.martijndwars.webpush.Utils;

public class WebPush {

    public static void main(String[] args) {
            try {
                new WebPush().exec();
            } catch (Throwable th) {
                th.printStackTrace();
            }
    }

    public void exec() throws Throwable {

            Security.addProvider(new BouncyCastleProvider());

            // jsonから取得と書いていますが、出力されたものをコピペです
            String endpoint = /* webPushに送られてくるjsonから取得(上記のendpointに相当) */;
            String userPublicKey = /* webPushに送られてくるjsonから取得(上記のp256dhに相当) */;
            String userAuth = /* webPushに送られてくるjsonから取得(上記のauthに相当) */;

            // Base64 string server public/private key
            String vapidPublicKey = /* 初めに作ったサーバーの公開鍵 */;
            String vapidPrivateKey = /* 初めに作ったサーバーの秘密鍵 */;

            // Construct notification
            Notification notification = new Notification(endpoint, userPublicKey, userAuth, getPayload());

            // Construct push service
            PushService pushService = new PushService();
            pushService.setSubject("mailto:admin@martijndwars.nl");
            pushService.setPublicKey(Utils.loadPublicKey(vapidPublicKey));
            pushService.setPrivateKey(Utils.loadPrivateKey(vapidPrivateKey));

            // Send notification!
            HttpResponse httpResponse = pushService.send(notification);

    }

    private byte[] getPayload() {
            JsonObject jsonObject = new JsonObject();
            jsonObject.addProperty("title", "おっはー");
            jsonObject.addProperty("message", "World");

            return jsonObject.toString().getBytes();
    }
}

これを実行すると、クライアントへとPush通知が飛ぶようになります。
別のメインクラスが実行されたりそもそも実行されなかったりするときは(たぶん普通には実行できないように思うのですが)、sbt compileをしてEclipseでインポートし直すなどしてみてください。それで実行できるのではないかと思います。もっと良い方法があるように思いつつ。

ここまでで必要な準備は終わったので、あとはさっき作ったWebPush.javaを実行してメッセージを送ってあげればデスクトップ通知ができるのではないでしょうか。たぶん。
というわけで実行しましょう!

f:id:s-masuda:20180719203906p:plain でけた!!!!

めでたしめでたし。

というわけでデスクトップ通知の大枠はできたのではないかと思いますので、あとは煮るなり焼くなり炙るなりすればきっと素晴らしいデスクトップ通知が。

こんな感じで、ほぼ入門程度の技術研修(それもJavaだけ!!)を終えたばかりの新卒なのに「AngularとPlay使ってデスクトップ通知実装してね!」などと突然言われてしまうような会社ではありますが、僕みたいな初心者でもこうしてちゃんと実装しきるところまで助けてもらえますので、もしよければどうぞ。そしてぜひ大阪に。

fresh-recruiting.goalist.co.jp

お待ちしております。

Swaggerを使ってRestful APIを記述する

こんにちは、ゴーリストのトゥアンです。

ウェブ開発でRestful APIはだんだん普及していると共に、Resful APIを記述するためにいろいろな仕方があります。

Swaggerを使うことは下記の通りに、いくつかの利点があります。

  • Amazon API Gatewayへ展開できる
  • フロントエンド開発のため、モックサーバーを作りやすい
  • OpenAPI仕様の基準に基づくAPIの入力(リクエスト)・出力(レスポンス)を定義できる

Swagger 3.0 は2017年07月にリリースされましたが、現在AWSはSwagger 2.0だけサポートしているので、今回の記事でSwagger 2.0について説明します。

Swaggerファイルのフォーマット

SwaggerでサポートしているフォーマットはYAMLとJSONです。

Swaggerファイルの構造は複数階層のオブジェクトです。ルートオブジェクトは「Swagger」というオブジェクトです。

1. Swagger オブジェクト

詳しい情報は下記のリンクにご参考ください。

https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#swagger-object

大切なフィールドは:

フィールド名 タイプ 説明
swagger String 必須です。"2.0"という固定な価値があります。
info Object 必須です。情報のオブジェクトです。後で説明します。
host String ホスト名又はホストのIPアドレスです。
basePath String 全部APIの共有なパスです
schemes String 全部APIのプロトコル(httpやhttps, ws, wssなど)です
paths Object 必須です。
全部APIを一つずつ記述するためのオブジェクトです。
後で説明します。
definitions Object 自由なオブジェクトを定義できます。後で説明します。

2. info オブジェクト

APIについてのメタデータです。 詳しい情報は下記のリンクにご参考ください。 https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#info-object

大切なフィールドは:

フィールド名 タイプ 説明
title String 必須です。アプリケーションのタイトルです
version String 必須です。API記述仕様書のバーションです。

3. paths オブジェクト

APIの記述情報です。 詳しい情報は下記のリンクにご参考ください。 https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#paths-object

大切なフィールドは:

フィールド名 タイプ 説明
/{path}
{path}は任意に定義できる
Object 定義されたpathに該当するAPIの記述です。
「Path Item」 というオブジェクトです。
後で説明します。

Path Itemオブジェクトの詳しい情報は下記のリンクにご参考ください。 https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#paths-item-object

大切なフィールドは:

フィールド名 タイプ 説明
get Object 定義されたpathに該当するGETメソードの記述です。
「Operation」というオブジェクトです。後で説明します。
put Object pathに該当するPUTメソードの記述です。
タイプはgetのOperationオブジェクトと同じです。
post Object pathに該当するPOSTメソードの記述です。
タイプはgetのOperationオブジェクトと同じです。
delete Object 定義されたpathに該当するDELETEメソードの記述です。
タイプはgetのOperationオブジェクトと同じです。
options Object pathに該当するOPTIONSメソードの記述です。
タイプはgetのOperationオブジェクトと同じです。
head Object pathに該当するHEADメソードの記述です。
タイプはgetのOperationオブジェクトと同じです。
patch Object pathに該当するPATCHメソードの記述です。
タイプはgetのOperationオブジェクトと同じです。
parameters Array 全部のメソッドの共有なリクエストパラメーターです。
配列要素のタイプは以下のいずれかです。
・definitionsに自由に定義したオブジェクトに参考
・「Parameter」というオブジェクト。後で説明します。

4. operation オブジェクト

定義されたパスの一つのメソッドのリクエスト・レスポンスの記述です。
詳しい情報は下記のリンクにご参考ください。

https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#operation-object

大切なフィールドは:

フィールド名 タイプ 説明
parameters Array メソードのリクエストパラメーターです。
配列要素のタイプは以下のいずれかです。
・definitionsに自由に定義したオブジェクトに参考
・「Parameter」というオブジェクト
responses Object レスポンスを記述するためのオブジェクトです。
後で説明します。

5. parameter オブジェクト

リクエストパラメーターの記述です。
詳しい情報は下記のリンクにご参考ください。

https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#parameter-object

大切なフィールドは:

フィールド名 タイプ 説明
name String 必須です。パラメーターの名前です。
in String 必須です。リクエストにパラメーターを含める場所です。
価値は以下のいずれかです。
・"query"
・"header"
・"path"
・"formData"
・"body"
required Boolean 定義されたパラメーターは必須かどうか記述です。

6. responses オブジェクト

APIレスポンスの記述です。 詳しい情報は下記のリンクにご参考ください。 https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#responses-object

大切なフィールドは:

フィールド名 タイプ 説明
{HTTP ステータスコード}
(400や404, 500など)
Object 定義されたHTTPステータスコードに該当するレスポンスの記述です。
タイプは以下のいずれかです。
・definitionsに自由に定義したオブジェクトに参考 
・「Response」 というオブジェクトです。後で説明します。

Responseオブジェクトの詳しい情報は下記のリンクにご参考ください。 https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#responseObject

大切なフィールドは:

フィールド名 タイプ 説明
description String 必須です。レスポンスの概念記述です。
schema Object APIレスポンスの構造詳細記述です。後で説明します。

7. definitions オブジェクト

自由なオベジェクトの定義です。
詳しい情報は下記のリンクにご参考ください。

https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#definitionsObject

definitions オブジェクトのフィールドは:

フィールド名 タイプ 説明
{name}
name: 定義したいオベジェクトの名前です。
Object 指定されたnameがあるオブジェクトの記述です。
タイプはresponseのschemaオブジェクトと同じです。
後で説明します。

SchemaオブジェクトはJSON Schema仕様書に基づいて定義されます。
JSON Schema仕様書は下記のリンクにご参考ください。
https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#definitionsObject 
また、JSON Schema仕様書の他にSwaggerのSchemaオブジェクトは特別なフィールドもあります。
詳しい情報は下記のリンクにご参考ください。

https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#schema-object

大切なフィールドは:

フィールド名 タイプ 説明
type String 定義したいオブジェクトのタイプです。
サポートしているタイプは:
原始タイプ("integer"や"string"など)、
"array"と"object"です

typeが"array"である場合、下記のフィルードがあります。

フィールド名 タイプ 説明
items Object 配列要素を定義するためのSchemaオブジェクトです。

typeが"object"である場合、下記のフィルードがあります。

フィールド名 タイプ 説明
required Array 必須なフィルード名の配列です。
properties Object オブジェクトのフィルードを定義するための各Schema オブジェクトです。

サンプル

課題:https://example.swagger.io/v1/users/{user_id} パスのGETメソッドとPUTメソッドを記述します。

GETメソッド:
リクエストのパラーメータ:
+ user_id : パスにある
レスポンスのオブジェクト:ユーザの名前とユーザのメールアドレス

PUTメソッド:
リクエストのパラーメータ:
+ user_id : パスにある
+ name: ボディにある
+ email: ボディにある
レスポンスのオブジェクト:成功な場合に200コードを返却して、失敗な場合に400コードと404コードを返却します。

上記のAPIを記述するために、下記のSwaggerファイルを作ります。

swagger: "2.0"
info: # infoオブジェクト
  title: "Swaggerの例"
  version: "1.0.0"
host: "example.swagger.io"
basePath: "/v1"
schemes:
- "https"
paths: # pathsオブジェクト
  /user/{user_id}:
    get: # getメソッドのoperationオブジェクト
      tags:
      - "userAPI"
      summary: "ユーザーIDによって、ユーザー情報を取得する"
      operationId: "getUserByUserId"
      produces:
      - "application/json"
      parameters: # parameterオブジェクトの配列
      - name: "user_id"
        in: "path"
        description: "ユーザーID"
        required: true
        type: "number"
      responses: # responsesオブジェクト
        200:
          description: "成功なレスポンス"
          schema: # schemaオブジェクト
            $ref: "#/definitions/User" 
            # definitionsに定義されたオブジェクトの参考
        400:
          description: "無効なユーザーID"
        404:
          description: "ユーザーを見つけていません"
    put: # putメソッドのoperationオブジェクト
      tags:
      - "userAPI"
      summary: "ユーザーIDによって、ユーザー情報を更新する"
      operationId: "updateUserByUserId"
      produces:
      - "application/json"
      parameters: # parameterオブジェクトの配列
      - name: "user_id"
        in: "path"
        description: "ユーザーID"
        required: true
        type: "number"
      - in: "body"
        name: "body"
        required: true
        schema: # schemaオブジェクト
          $ref: "#/definitions/UpdateUserReq" 
          # definitionsに定義されたオベジェクの参考
      responses: # responsesオブジェクト
        200:
          description: "成功なレスポンス"
        400:
          description: "無効なユーザーID"
        404:
          description: "ユーザーを見つけていません"
definitions: # definitionsオブジェクト
  User: # schemaオブジェクト
    type: "object"
    properties:
      id: # schemaオブジェクト
        type: "integer"
        format: "int64"
      name: # schemaオブジェクト
        type: "string"
      email: # schemaオブジェクト
        type: "string"
  UpdateUserReq: # schemaオブジェクト
    type: "object"
    required:
      - "name"
      - "email"
    properties:
      name: # schemaオブジェクト
        type: "string"
      email: # schemaオブジェクト
        type: "string"

Swagger UI」を使って、記述したAPIは綺麗に表示できます。

API概要の表示 f:id:t-nguyen:20180709155906p:plain GETメソッドの表示 f:id:t-nguyen:20180709154630p:plain PUTメソッドの表示 f:id:t-nguyen:20180709154752p:plain

ツール

Swaggerを書くために、「Swagger Editor」を使って、エラーがある場合、すぐ報告は出ます。

Swaggerファイルを作成した後で、「Swagger UI」を使って、記述したAPIは綺麗に表示できます。

この二つのツールをローカル環境にダウンロードすることと、ウェブブラウザーにオンラインで使うことと、どちらもできます。

参考書類

今回の記事でSwagger 2.0について少し説明しました。
詳しい情報は下記のリンクにご参考ください。

バーション2.0 : https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md
バーション3.0 : https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md

【求人情報ビッグデータ】HR EXPO 2018出展しています

こんにちは、開発部の飯尾です。
昨日7月11日水曜日、タイトル通りHR EXPOに参加してきました!

www.hr-expo.jp

企業の人事担当者さま向けに、サービス紹介・商談を行うイベントです。
ゴーリストもHR Tech領域で自社サービスを紹介してきました。

日本中の求人情報ビッグデータから、採用を読み解くHRogシリーズ

f:id:y-iio:20180712120616p:plain

https://chart.hrog.net/chart.hrog.net

map.hrog.net

ブースの様子
自社メディアのキャラクター「ふろぐん」カラーのTシャツで目立つ…

f:id:y-iio:20180712113445j:plain

私は現在、HRogチャートHRogマップの開発・運用を担当しています。
ときおり既存ユーザーの営業訪問に同行することはありますが
ど新規の営業イベントに参加するのは初です!

あんまり力になれてるわけでは無いですが

  • 呼び込みスクリプトの考案
  • とりあえず声出して足を止めてもらう

などなど、自分にもできることをがんばりました〜
普段営業メンバーがさらっとサービス紹介していることのすごさを実感ですね。

とはいえ「アルバイト採用担当されてるんですか?私が作ったサービスでこういうのあるんです!よかったらデモ触っていきませんか!」の最高の流れが実現できたので
個人的には満足です、ガハハ


このHR EXPOは明日13日金曜日まで開催中です。
ぜひゴーリストブースへ遊びに来てください!
\カワイラシイふろぐんうちわも配ってます/

こういうお祭り的なイベントもオモチロイものですね。
窓の外に出ると己の姿もよく見えると思います。

技術ブログとはいえ、採用のためにやってるものなので
社内エンジニアがどんな仕事してるかとか会社の雰囲気もこんなかんじで紹介していきます💪('ω'💪)

Gitがやばい!(1/3)

最近初めての開発者が多いプロジェクトに参加しました、そのためgitの強さがよ〜くわかりました(やらかしながらね)。シンプルから複雑な動き:つまり、「自分一人でバージョ二ングだけのために使ってる」場合(シンプル)から「ヤベー、いろんな人がいるし、ギットからデプロイされてるし、何が起きてる?!」(複雑)な状況に便利なものを軽く通しておくと便利かなと思いました。(以外と長かったため、この記事には基本:Level 0 とLevel 1を書いて見ました)

https://assets-cdn.github.com/images/modules/open_graph/github-mark.png

Level 0: init, .gitignore, status, remote

まずは環境設定みたいなものですね。プロジェクトフォルダーに git initを書きますとそのフォルダーがレポジトリとして初めて使えるようになります。 最初の状態で何も「トラッキング」されてないです。

status

git statusは現在の状況を確認するためのコマンドです。 git status を打ちますと、そのフォルダーの内容が全部出て、”untracked files”としての一覧になってます。 f:id:c-pattamada:20180705160140p:plain

.gitignore

このファイルの中に、gitを無視して欲しいファイル・パスなどを書きます。特に、プロジェクのlibフォルダーのような、外部ライブラリーのファイルを普通はのぞいて欲しいために、そういうパスを入力すれば良いです。 また、個人エディタ用のフォルダー(.ideaなど)も含めた方がいいかと思います。 この場合は ‘fileToIgnore.txt’を無視しましょう、ご覧の通り、lsには見えますがgitの方がむししているのです。

f:id:c-pattamada:20180705160159p:plain

入力後、またgit statusを打つと、ちゃんと無視してると確認できます。

f:id:c-pattamada:20180705160213p:plain

remote

Gitが複数の人たちが同じプロジェクトに開発できるために作られてますのでオンライン上にも置かれてるケースが多いです。ローカルのgit repositoryをオンラインに繋ぐのに githubでrepositoryを作る必要があります。 これを行うために、githubでのrepositoryも存在する必要があります。以下の記事のstep 2の画像を参考にしてください)

Deploying an Angular app to GitHub Pages - Goalist Developers Blog

Repository のurl をコピーして、
git remote add origin <url>
のようにローカルのgitのremoteを設定できます(originがそのサーバーの名前になりますが、他の名前でも大丈夫です)。

Level 1: Add, Commit, push, checkout, reset/revert.

残りのファイルを「トラッキング」したいと思いますので一旦プロジェクトフォルダから git add . を打ちましょう、そして、git statusで…. 緑! (ファイル一個ずつもadd できます。git add <ファイル名>)

f:id:c-pattamada:20180705160239p:plain

現在の緑のファイルをcommit する準備が整ってる。

と言っても… commitとは?

Commitはギットの基本技です。「add」されたファイルの状況を保存するようなことです。まさにゲームでボスの前に保存してから、ボスを倒した後でまた保存するように使うイメージです。何かがうまく行かなかった場合に、ボスの前のコミットに戻せます。

Commitするとどういう情報が保存されてるでしょう? それは以前のコミットと比べて、変更したファイル(何行目を変更したなど)の情報です。後に現れる「Merge」でこれが重要となります

Commitするために: git commit -m "コミットについての一言" を使えば便利だと思います -m は 「メッセージ」(コミットについての一言)を追加するためにあります。

Commitがそれぞれユニークなhashをもつ、これによってギットがコミットを管理します(英語ですが、How is git commit sha1 formed · GitHub で、そのハッシュがどうやって作られてるのかを調べた人もいました)

では、git log でコミット歴史を見ましょう

f:id:c-pattamada:20180705160518p:plain

黄色文字列が今回のコミットハッシュです。あとでハッシュを使うものも見ましょう! (ボーナス:githubのコミットの管理画面からいかの画像のようなコミットが見れます、そこで右側の青いものをクリックするとhashをコピーできます ^^) f:id:c-pattamada:20180705160748p:plain 

Commitしました!で..?

ローカルのgitを定期的にオンラインにpushしましょう!これで、バックアップとなり、他の人とシェアしたり共同開発ができるようになります! 先ほどのlevel 0で設定したoriginがpush先になります。 では、pushしましょう! git push -u origin masterを打てば、現在使ってるmasterのbranch(後で詳細書きます)がオンラインにも現れます。 -u が最初の時に使った方がいいですが、その後でgit pushでできます。

これでオンライン上のgit repositoryに行きますと、自分のpushの結果が見れます。

あー間違いました!

だいたい二つの状況があるかと思います。

コミットする前に、やっぱりいまの変更が失敗だと判断して、前のセーブに戻りたい

つまり、ボスに倒されて、直前のセーブの状態にresetをしたい。 まだコミットしてない状態なら、 git checkout <ファイル名> を使って、ローカルの変更を捨てられます。 注意:捨てられた変更は失われます。

一つのファイルではなく、全ファイルの場合に git checkout .が使えます。(両方ともremoteが設定されてる前提です。)

コミットをした後で後悔中です!

こういうのもよくあるかと思います。間違ったファイルをcommitしたり、アップしたら他の人が作ってる部分がなぜか壊れたりするケースもあるかと思います。ボスに負けたのに、間違って保存しちゃった気分って最悪ですが、gitの場合には問題ありません。

手段1: git reset そのままに使うと、「add」されたファイルが全てaddされてない状態になるのですが、ローカルの内容を全て捨てたい場合もあります(他の人とかぶりがありました!など)。

git reset --hard origin/<branch_name> [(branch_name_)を確認するために git branch を書いて、*がついてるのが現在のブランチ] 
注意:このやり方でローカルのものが無くなります。 十分注意してください。

手段2: もし、すでにプッシュされた、昔のものであれば、git revert <commit hash> で昔にあったコミットを戻すような新しいコミットを作成します。このやり方では何も無くなりませんが、実装がちょっとめんどいです。 例えば、今回のコミットをrevertしたい場合には git revert 0e7cfc6b8cac249f8fd2445c86660d6805045b9d を書きますと....

f:id:c-pattamada:20180705161159p:plain

あら...コミットが消えましたね。今回作成したファイルも消えました!

まとめ

今回Gitの基本コマンドについて書きました。init, status, add, push, commit, resetなどを使って見てください! 以外と説明が長くなってしまいました。そのため、全部一つに記事に打っ込むよりみつに分けることにしました!続きも楽しみにしてください!今度はLevel 2: branch, merge, fetch, pullなどについて書きますね!

IaC by Terraform ~DAY5: Terraform Commands (4)~

どうも,エンジニアのナカノです.

今月は梅雨の時期でしたね。洗濯物を外に干せないことが多く、悶々としておりました.

天気予報を見ると今週から天気が良いみたいなので、少しホッとしております.

さて,今回も前回の記事の内容の続きとなります.

前回はterraform.tfstateの管理/運用コマンド (1)に関する内容でした.

今回の内容は,terraform.tfstateの管理/運用コマンド (2)に関してです.

目 次
  • `terraform.tfstate`の管理/運用コマンド (2)
    • taint
    • untaint
    • refresh
    • workspace
      • list
      • select
      • new
      • delete
  • 最後に

terraform.tfstateの管理コマンド (2)

taint

Terraformを使っていると,applyの実行でエラーが出てしまって適当なリソースが想定とは異なる形で作成されることがしばしばあります.

そういった時は,まずはエラー箇所を修正し,次にそのリソースを作り直して想定通りの状態でデプロイしたい欲求に駆られるでしょう.

taintというコマンドはその様な時に役に立ちます.例えば,訳あって作成済みのリソースxxxxxxxxを作り直したいとします.

そのためにterraform taint xxxxxxxxを実行すると,terraform.tfstateに記載されているxxxxxxxxの項目taintedの値がfalseからtrueに変わります。

taintedの値がtrueになると,xxxxxxxxはまずTerraformによって削除され,次にxxxxxxxxが再作成されます.

こういった状況ではゴリ押しでapplyを実行しまくるのも一つの手ですが,taintコマンドを上手く使う方が得策かもしれません.

www.terraform.io

  

untaint

untaintコマンドは,文字から分かる様に,taintとは反対の挙動を起こすコマンドです.

まずはtaintの挙動を思い起してみましょう.このコマンド実行すれば,指定されたリソースがtaintedな状態になります.

一方,untaintを実行すると,指定されたリソースがuntaintedな状態であるとしてterraform.tfstateに記録されます.

taintedなリソースはTerraformにより再作成されるのですから,untaintedなリソースはそのままの状態で保持されます.

このことをterraform.tfstateの観点で言えば,taintはtaintedの値をtrueに変えるがuntaintはtaintedの値をfalseに変えます.

www.terraform.io

  

refresh

一般に,Terraformで管理されているリソースの情報はterraform.tfstateで管理されています.

しかし,Terraform以外の手段によってTerraformの管轄対象のリソースが更新された場合,`terraform.tfstate‘の内容は古いものとなります.

この様な状況では,applyの実行の際に思わぬ挙動が起きて痛い目を合うことがあります.そういう時こそrefreshコマンドの出番です.

このコマンドは,Terraformが管轄するリソースに合わせてterraform.tfstateの内容を更新してくれます.

例えば,何らかの問題が起こって,管理コンソールでTerraformに管理されているリソースの更新を行うことがあります.

そんな時にrefreshコマンドを実行すると,実態のリソースたちとterraform.tfstateの間に差分が生まれない様にすることが出来ます.

このコマンドは,問題が起きた時に威力を発揮することが多いので,覚えておくべきものかと思います.

www.terraform.io

  

workspace

workspaceを使うと,terraform.tfstateの切り替えを実現することが出来ます.このことをもう少し具体的に説明します.

初めはdefaultというworkspaceしかないのですが,新しく作成してworkspace1workspace2というworkspaceを作ったとします.

そして,workspace1workspace2の各々でリソースを作成すると,各々のworkspaceに応じた‘terraform.tfstate`が生成されます.

これらのファイルは,ローカルでは

  • ./terraform.tfstate.d/workspace1/terraform.tfstate
  • ./terraform.tfstate.d/workspace2/terraform.tfstate

の様に配置されます.一方で,バックエンド設定をしている場合でも殆ど同様な感じで各々のterraform.tfstateが格納されます.

この時,workspace毎のterraform.tfstateは互いに独立しており,リソース作成の状態の上書きは起こりません.

よって,このコマンドを使えば,リソース作成の状態を適切に管理/運用することが出来る様になります.

しかし,.tfファイルの内容はworkspaceでは切り替わらないため,現状では共通の設定をステージ毎に管理する場合に役立つという感じでしょうか.

さて,以下はworkspaceのサブコマンドlistselectnewdeleteの説明となります.

  

list

このサブコマンドは,存在する全てのworkspaceをリストします.また,現在のworkspaceには*が付いています.

例えば,存在するworkspaceがdefalutworkspace1workspace2であり,現在のworkspaceがworkspace1であるならば,listの実行結果は下記の様になります.

$ terraform workspace list
  default
* workspace1
  workspace2

www.terraform.io

  

select

現在のworkspaceから別のworkspaceに切り替えたい時に,このサブコマンドを使用します.

例えば,存在するworkspaceがdefalutworkspace1workspace2であり,現在のworkspaceがworkspace1であるとします.

この時に,現在のworkspaceからworkspace2に切り替えたい時は,以下の様に実行します.

$ terraform workspace select workspace2
Switched to workspace "workspace2".

www.terraform.io

  

new

これを実行すると,新しいworkspaceを作成することが出来ます.例えば,new_workspaceというworkspaceを作成する場合は下記を実行します.

$ terraform workspace new new_workspace
Created and switched to workspace "new_workspace"!

You're now on a new, empty workspace. Workspaces isolate their state,
so if you run "terraform plan" Terraform will not see any existing state
for this configuration.

www.terraform.io

  

delete

workspaceを削除する場合は,このサブコマンドを利用します.ただし,以下の点に注意する必要があります.

  • 削除対象のworkspaceがそもそも存在する
  • 削除対象のworkspaceに対応するterraform.tfstateの内容が空である
  • 削除対象のworkspaceは現在のworkspaceではない

これらの点を守った上で,例えばworkspace_to_deleteを削除したい場合は以下を実行します.

$ terraform workspace delete workspace_to_delete
Deleted workspace "workspace_to_delete".

もしterraform.tfstrateの内容が空ではない場合にworkspace_to_deleteを削除したいならば,-forceオプションを使えばよいです.

ただし,このオプション付きでdeleteを実行すると,workspace_to_deleteの場合でTerraformにより作成されたリソースはTerraformの管理外となってしまいます.

www.terraform.io

  

最後に

今回はterraform.tfstateの管理/運用コマンド (2)を紹介させて頂きました.

前回と同様で,使い所を適切に考えれば割と役に立つコマンドばかりです.

直近はTerraformコマンドを紹介してきましたが,コマンドの紹介は今回で終わりです.

次回からは,また別のトピックを扱おうかと思います.

それでは,今回はこの辺りで筆を置かせて頂きます.

次回に乞うご期待下さい.

Deploying an Angular app to GitHub Pages

Being a programmer I can't stop myself greeting you with Hello World so

f:id:vivek081166:20180620155225p:plain

Do you want to tell the world about all the great things you are building?
Well, GitHub Pages might help you to do that.

In this blog, I would like to share the process of bringing your Angular app from local to the internet

http://localhost:4200 ==> https://<username>.github.io/<repository-name>

Let's walk through the following steps to deploy our test-app on GitHub Pages

f:id:vivek081166:20180620142950p:plain

So let's get started...

Step 1 : Build your Angular app

With Angular CLI, using the following command; build your Angular app

ng build

After building your app all we need is the contents of the dist folder

f:id:vivek081166:20180620130056p:plain

Step 2 : Create a repository

For the purpose of demoing your app, it is good idea to have a separate repository.
So, let's create one for our test-app and name it as test-app-demo

f:id:vivek081166:20180620143918p:plain

Step 3 : Initialize repository for file upload

The simplest way to initialize files upload via GitHub will be to add .gitignore to your repository.

f:id:vivek081166:20180620155443p:plain

and commit your file

f:id:vivek081166:20180620155529p:plain

Step 4 : Upload your build files

Having done the previous step you can now upload the build files via GitHub

Click the Upload Files button on master branch

f:id:vivek081166:20180620155853p:plain

Drag your build files here or simply click choose your files

f:id:vivek081166:20180620155938p:plain

Go to the project source folder and choose dist folder

f:id:vivek081166:20180620160410p:plain

f:id:vivek081166:20180620160656p:plain

Select all the build content and upload

f:id:vivek081166:20180620161209p:plain

Commit your uploaded file to mater-branch

f:id:vivek081166:20180620161629p:plain

Step 5 : Update the base URL

Since we uploaded build files into the separate repository, we need to update the base URL to let angular know about the root directory

Update index.html file

f:id:vivek081166:20180620162455p:plain

Change base tag from
<base href="/">
to the name of the repository
<base href="/test-app-demo/">

f:id:vivek081166:20180620162627p:plain

f:id:vivek081166:20180620162704p:plain

Step 6 : Set up the GitHub Pages

This is the final step and we are almost done!

Navigate to the Settings tab of the repository for setting up a GitHub Page for our Angular app

Settings option is only visible to the owner of the repository

f:id:vivek081166:20180620162851p:plain

scroll down a little and you are almost there...

f:id:vivek081166:20180620163319p:plain

f:id:vivek081166:20180620163456p:plain

Select master-branch as the source of GitHub Pages

f:id:vivek081166:20180620163835p:plain

Save the changes and you are done!!

f:id:vivek081166:20180620163958p:plain

しばらくお待ちください。。。
Wait while GitHub sets up everything for you and holla!! your app is live

f:id:vivek081166:20180620164139p:plain

Your app will be live on the web address shown in the GitHub Pages' section. https://<username>.github.io/<repository-name>
Yes!! we did it.

f:id:vivek081166:20180620164542p:plain

Thank you very much for reading all to the end.

I'll be coming back soon with another such topic. Till then
Happy Learning :)