20190330のvue.jsに関する記事は8件です。

SpringBoot+Vueなアプリケーションを構築する

目標

  • サーバーサイドはSpring(Java)
  • フロントはVue
  • npmの恩恵を受けたい
  • アウトプットはJarだけ

リポジトリ

https://github.com/iwanagat85/spring-boot-vue

構築

ディレクトリ構成

  • Spring Initializr等で出来たSpringプロジェクト直下にwebディレクトリを作成
  • そこにvue-cliを使ってvueのプロジェクトを作成する
.
├── .gitignore
├── build.gradle
├── gradle
├── gradle.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
├── src
|   ├── main
|   └── test
└── web
    ├── public
    ├── src
    ├── .gitignore
    ├── babel.config.js
    └── package.json

VueProjectの設定

  • デフォルトのポートがSpringと被るので8081へ変更
  • build時の出力先を/src/main/resources/static/に指定
package.json
  "scripts": {
    "serve": "vue-cli-service serve --port 8081",
    "build": "vue-cli-service build --dest ../src/main/resources/static/",
    "lint": "vue-cli-service lint"
  }

SpringProjectの設定

  • 画面のリソースはVue側で自動生成するのでstaticディレクトリを.gitignoreにまるっと登録
.gitignore
src/main/resources/static/
  • リソース生成時にvue側のbuildを実行する
  • --prefixnpmを実行するディレクトリを直接指定する
build.gradle
task npmRunBuild() {
    if (!file("${rootDir}/web/node_modules").exists()) {
        "npm --prefix ${rootDir}/web install ${rootDir}/web"
                .execute()
                .waitForProcessOutput(System.out, System.err)
    }
    "npm --prefix ${rootDir}/web run build"
            .execute()
            .waitForProcessOutput(System.out, System.err)
}

processResources {
    dependsOn npmRunBuild
}

実行

$ ./gradlew bootRun

その他

  • npmのビルドが毎回走るから起動が物凄く遅い、、、
  • フロントエンド作成時はnpm run serveしながら
  • APIでのやりとりはSpring側とportが違うからCORSで怒られる
    • localhost:8081を許可しなきゃいけないけど、、、
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

はてなブログの歴代一位をまとめて見れるサイトを作った

はじめに

はてなブログのテクノロジーカテゴリをよく見ています。
今までの歴代一位がどのようなものだったか気になって一覧で見れるサイトを作りました。

https://hatena-hotentry.netlify.com/

使った技術

  • serverlessFramework
  • go
  • goquery
  • RealtimeDatabase
  • lambda
  • vue

バッチ

はてなブログのデータを取得するスクリプトをバッチで動かしています。
はてなブログをスクレイピングするコード(go)を書き、ServerlessFrameworkを使ってlambdaにアップロードしています。
ServerlessFrameworkは serverless.yml にcronを定義しておけば、cloudWatchを使ったlambdaのスケジュール実行を設定してくれるのでお手軽にバッチ処理が作れます。

参考:https://dev.classmethod.jp/etc/serverless-framework-lambda-cron-execute/

スクレイピングしたデータを保存する場所としてはfirebaseのRealtimeDatabaseを使いました。

初めはCloudFirestoreの方に保存していたのですが、保存しているドキュメント数が5000近くになり、無料プランでの一日のドキュメント読み取り数が50000だったため、10回全件取得すると、その日はデータ取得ができなくなる状態になりました。
そのため、RealtimeDatabaseに乗り換えましたが、NoSQLにデータを保存する際の設計がわかっていれば、CloudFirestoreでもうまくできたような気がします。

フロント

vueを使いました。vue-cliで雛形を作り、それをいじって作っています。
デプロイにはnetlifyを使っています。

最後に

goとvueを勉強するために作りましたが、こういう小さいアプリケーションを色々作っていきたい気持ちです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

v-cardをクリックしたらv-dialogが開くサンプル

Vuetifyの公式サイトには、v-btnをクリックしてv-dialogを開くサンプルが掲載されています。1

ここでは、v-cardをクリックしたときにv-dialogを開くサンプルを作成してみました。

長い文章を小さめのカードで表示するとき、v-cardでは省略形で表示しておき、そこをクリックしたら全文がv-dialogで表示されるようになっています。

サンプル

CodePenで動くサンプルを作ってみました。

なお、元は単一ファイルコンポーネントとして「CardTest」を定義し、それをApp.vueで複数並べる使い方をしていました。
しかし、CodePenではそのままだとうまく動きませんので、propsで定義していたtitleとcontentをdata()に移動させてあります。

See the Pen Vuetify Sample - click card, open dialog by shozzy (@shozzy) on CodePen.

ポイント

data()で定義されているdialogの値が、v-dialog要素のv-model属性にバインディングしてあります。
デフォルトではfalseなのでv-dialogは非表示です。
v-cardの方でクリックイベントを拾い dialog = true にすると、v-dialogが表示される仕掛けです。

単一ファイルコンポーネントのインスタンスごとに状態を持っているので、App内に複数のカードを並べてあっても、正しく各カードの状態を保持できます。

<template>
  <div class="cardtest">
    <v-card min-width="200px" height="100%" @click.stop="dialog = true">
      <v-card-title>{{ title }}</v-card-title>
      <v-divider></v-divider>
      <v-card-text>{{ shortenedContent }}</v-card-text>
    </v-card>

    <v-dialog v-model="dialog" scrollable max-width="80%">
      <v-card>
        <v-card-title>{{ title }}</v-card-title>
        <v-divider></v-divider>
        <v-card-text height="300px">{{ content }}</v-card-text>
      </v-card>
    </v-dialog>
  </div>
</template>

<script>
export default {
  name: 'CardTest',
  props: {
    title: String,
    content: String
  },
  data(){
    return {
      dialog: false
    }
  },
  computed: {
    shortenedContent: function(){
      let maxlength = 100;
      if(this.content.length <= maxlength){
        return this.content;
      }
      else{
        return this.content.substr(0,maxlength-10)+'...(続きを読む)'
      }
    }
  }
}
</script>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vuejsでのプロトタイピングを簡単にできるアプリが欲しかったので作っている話

はじめに

タイトルの通り作っているものがあるが、公開することで既にこれできるよみたいな情報も得られると思ったので公開しようと思った。

本当はもう少し作りこんでから公開したかった。
ただ、残業が多すぎてアプリケーションが作れないから転職しようとしていて、エージェントから公開しているGitHubとかQiitaのアカウントありますかと聞かれたからROM専から卒業しようと思った。

とりあえず動くもの

https://sterashima78.github.io/vue-webpage-builder

とりあえず動いている絵

img.gif

作っている動機

基本的には READMEに書いておいたが、書きなぐりました感が強いのでもう少し丁寧に書く。

似ていたもの

既存のもので十分な人もいると思うので、作る前に調べていた時に触ったものを書いておく。

  • Vuegg
    • いいツールだったのだが、次に書く私のほしかったものには合わなかった
  • Grapes
    • 初めはこれのプラグインを作ろうとしたのだが、無理やり感が出てきて断念した

ほしかったもの

  • 実用的なソースが出力される
    • プロトタイピングに使ったあとで、じゃあこれで行こうとなった時に全部書き直すよりも一部直せば済むほうが嬉しい
    • Vueggはコンポーネントの配置が絶対座標だった。絵を作るのには適していたのだけど、出力をそのまま使う気にはならなかった
  • 任意のコンポーネントを利用できる
    • Vuetifyや、Elementなどたくさんコンポーネントフレームワークがあるので、任意のものが使えてほしい
    • 自分が作ったコンポーネントも利用できるようになっていてほしい
    • Vueggは単一のコンポーネントフレームワークしか利用できなかった (Issueにもなっている)

どうするか

任意のコンポーネントを利用するという点はアプリケーションを構築に利用しているVueインスタンスと、アプリケーション内で構築されるプロトタイプで利用するVueインスタンスが同一であると絶対に解決できないと考えた。

初めはインスタンスを複数作って Shadow DOMを使えば何とかなるとも考えたけど素直にiframeを使うことにした。

  1. iframe要素内でscriptタグでvuejsを読み込み、アプリケーションからiframe内のVueインスタンスを参照する
  2. メインのアプリケーション部分でvnodeに相当するツリーを構築する
  3. iframe内のvueに渡して renderメソッド で描画

これができれば、iframe内で任意のスクリプトを読ませる機能を作ることで、『ほしかったもの』に書いた、任意のコンポーネントが利用できるの要件を満たせる。

面倒だったこと

Vuex Store は複数のインスタンスで共有できない

アプリケーションで構築するvnodeデータをVuexで管理して、そのデータをiframe内のvueでも共有して renderでstateを参照できれば一番シンプルだと考えて、Vuexベースで作ってた。(入門したてのTypeScriptでstoreに型つけるにはどうすればいいんだとか調べてまぁまぁ時間かかった)。
いざ描画しようと思ったら、できないということを知って困った困った。

結局 Rx を併用して無理やりやっている感がある。

Vueに登録されている全コンポーネントが知りたい

アプリケーションの性質的に利用できるコンポーネントがリストされていて、選択できないといけないが、それを取得するためのインターフェースが見つからなかった。

devtoolsとか見ていたら方法がありそうだったので調べたら、Vue.options.components に入っていた。

おわりに

Vuejsでのプロトタイピングを簡単にできるアプリを作っているという話でした。
もしも、上に書いた『ほしかったもの』をカバーするサービスやアプリケーションがあれば教えていただけると嬉しいです。

以下が、今後やりたいこと。

  • 追加したい機能
    • 任意のコンポーネントフレームワークを使えるようにする (いまはvuetifyのみだけど仕組み的には可能なのですぐやりたい)
    • 複数ページへの対応 (vue-routerで)
    • 作成途中の保存・読み込み
  • 機能以外で対応したいこと
    • リファクタリング
    • CI・CDの導入
      • ある程度機能がそろったら
  • 残業が減ってアプリケーションを作れるようになる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Google Books APIとVue.jsを使ってシンプルなBook Finder appを作る

このアプリを作ろうと思ったキッカケ

2月末にChingu Voyageという海外のプロジェクトに参加し、見事に制限時間内に課題のGoogle APIを使ったBook Finder アプリを作ることができずプロジェクトから落ちました。(笑)

というのも私はずっとrailsでしか勉強をしていなくてAPIはなんとなくJSONだったりaxiosが必要だということは分かっていたのですが、Javascriptを本格的に勉強したことがなく、プロジェクトでJavascriptが必要と分かった瞬間必死でネット上の教材を探したのですが、なかなか希望するものがなく・・・
いや写経してもなんの意味もないなと気づいたので、最近勉強をしているVue.jsを使って自分で作ってみようと思いました。

こんなアプリができます

↓こんなアプリが最終的にできます。Vue.jsだと直感的にコードが理解しやすく好きです。
Google books api app
※モバイルでみるとボタンの文字がおかしいですがまた調整します。

1 検索したい本の名前や、著者名を入力
スクリーンショット (438).png

2 本の名前、画像、著者名をapiから取得し、表示させる
スクリーンショット (436).png

3 SHOW DETAILボタンで、googlebooksのサイトに遷移し、本の詳細を見る
スクリーンショット (437).png

ソースコードと解説

main.js
const vm = new Vue({
  el: '#app',
  data() {
    return{
      query:'',
      items:[], #apiから取得したデータを格納
      }
    },
  methods:{
    getResult(query){
      axios.get("https://www.googleapis.com/books/v1/volumes?q=search" + query).then(response => {
        console.log(response.data);
        this.items = response.data.items;
        });
      }
    }
});

index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Book Finder</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
    <link rel="stylesheet" href="main.css">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
  </head>
  <body>
    <div class="container" id="app">
      <h3 class="text-center">Book Finder</h3>
      <p> Search:<form v-on:submit.prevent="getResult(query)">
        <input type="text" placeholder="Type in your search" v-model="query" /></p> #フォーム入力したものをv-modelでバインド
          <div class="book_wrapper">
            <div class="book_card" v-for="item in items">


              <div class="row no-gutters">
                <div class="col-md-4">
                  <img class="card-img" v-bind:src="item.volumeInfo.imageLinks.thumbnail" />
                </div>

                <div class="col-md-8">
                  <div class="card-body">
                    <h5 class="card-title">{{item.volumeInfo.title}}</h5>
                    <p class="card-text">{{item.volumeInfo.authors}}</p>    
                    <p class="card-text"><small class="text-muted">{{item.volumeInfo.publisher}}</small></p>    
                  </div>

                  <div class="book_button">
                    <a v-bind:href="item.volumeInfo.previewLink" class="waves-effect waves-light btn" target="_blank">show detail</a>
                  </div>
                </div>
          </div>
        </div>
       </div>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> #vue.jsのインストール
    <script src="main.js"></script> #jsファイルを読み込む
    <script src="https://cdn.jsdelivr.net/npm/axios@0.17.1/dist/axios.min.js"></script> #axiosをインストール
  </body>

</html>

main.css
.book_wrapper{
    width:100%;
}

.book_card{
    margin:10px;
    float:left;
    box-sizing: border-box;
    -webkit-box-sizing: border-box;
    width:40%;
}

.book_button{
    text-align:center;
}

参考知識

利用したVue.jsやaxiosについて下に参考メモを記入しておきます。

Vue.jsのインストール

CDNで用意されているので、index.htmlに貼り付けましょう。
詳しくはインストールを参照してください。

JSONデータとaxiosでgetしたデータについて

今回はGooglebooksAPIを利用してアプリを作りました。
ドキュメントはこちら

例えばURLに
https://www.googleapis.com/books/v1/volumes?q=search+harry このようにいれると、harryと名前がつくものを検索することができます。
これを検索欄に入力すると画面いっぱいに以下のようなJSONデータが表示されます。

{
 "kind": "books#volumes", //books#volumesの種類で検索
 "totalItems": 561, //561個のデータがある
 "items": [  //そのうちのitemsは以下のとおり
  {
   "kind": "books#volume",
   "id": "8VEJo-StWrkC",
   "etag": "n0F3h00LHMo",
   "selfLink": "https://www.googleapis.com/books/v1/volumes/8VEJo-StWrkC",
   "volumeInfo": {
    "title": "The Search for Harry C", //タイトル
    "authors": [
     "Mort Altshuler & Irv Susson" //著者
    ],
    "publisher": "Xlibris Corporation", //発刊者
    "publishedDate": "2001-05-14", //発刊日
    "description": "Harry Connors is at Penn State and in love with Sarah, a fellow student. They marry shortly after they are graduated. He gets a job with a contract agency of the Atomic Energy Commission involved in research programs related to atom bomb radiation effects, trigger devices and other secret projects. Harry´s work takes him to New Mexico, Philadelphia Naval Air Station and Indian Springs Air Force Base, Nevada. On the day that Harry disappears, a test atom bomb is detonated, an unmanned plane flown near the mushroom cloud to collect data explodes and a top-secret plan goes missing. Harry is never found and the government´s investigation circumstantially determines that Harry was an agent of the Soviet Union. Sarah, pregnant, returns to Pennsylvania. Four decades later, Harry´s son, Adam, sets out on an intriguing mission to find out what happened to his father. Along the way, with the help of his fiancee and two of his father´s fraternity brothers, he finds an aged member of the Russian spy machine, ex coworkers and a host of some very odd folks.", //本の説明
    "industryIdentifiers": [
     {
      "type": "ISBN_13",
      "identifier": "9781465315236"
     },
     {
      "type": "ISBN_10",
      "identifier": "1465315233"
     }
    ],
    "readingModes": {
     "text": true,
     "image": true
    },
    "pageCount": 211,
    "printType": "BOOK",
    "categories": [
     "Fiction"
    ],
    "maturityRating": "NOT_MATURE",
    "allowAnonLogging": false,
    "contentVersion": "0.1.0.0.preview.3",
    "panelizationSummary": {
     "containsEpubBubbles": false,
     "containsImageBubbles": false
    },
    "imageLinks": { //smallThumbnailとthumbnailはどちらも画像を表示する
     "smallThumbnail": "http://books.google.com/books/content?id=8VEJo-StWrkC&printsec=frontcover&img=1&zoom=5&edge=curl&source=gbs_api",
     "thumbnail": "http://books.google.com/books/content?id=8VEJo-StWrkC&printsec=frontcover&img=1&zoom=1&edge=curl&source=gbs_api"
    },
    "language": "en",
    "previewLink": "http://books.google.co.jp/books?id=8VEJo-StWrkC&pg=PA69&dq=search+harry&hl=&cd=1&source=gbs_api", //今回は詳細ページをこちらにします。
    "infoLink": "https://play.google.com/store/books/details?id=8VEJo-StWrkC&source=gbs_api",
    "canonicalVolumeLink": "https://market.android.com/details?id=book-8VEJo-StWrkC"
   }, //ここから下はこのアプリでは使わない情報です。
   "saleInfo": {
    "country": "JP",
    "saleability": "FOR_SALE",
    "isEbook": true,
    "listPrice": {
     "amount": 1404.0,
     "currencyCode": "JPY"
    },
    "retailPrice": {
     "amount": 1264.0,
     "currencyCode": "JPY"
    },
    "buyLink": "https://play.google.com/store/books/details?id=8VEJo-StWrkC&rdid=book-8VEJo-StWrkC&rdot=1&source=gbs_api",
    "offers": [
     {
      "finskyOfferType": 1,
      "listPrice": {
       "amountInMicros": 1.404E9,
       "currencyCode": "JPY"
      },
      "retailPrice": {
       "amountInMicros": 1.264E9,
       "currencyCode": "JPY"
      }
     }
    ]
   },
   "accessInfo": {
    "country": "JP",
    "viewability": "PARTIAL",
    "embeddable": true,
    "publicDomain": false,
    "textToSpeechPermission": "ALLOWED",
    "epub": {
     "isAvailable": true,
     "acsTokenLink": "http://books.google.co.jp/books/download/The_Search_for_Harry_C-sample-epub.acsm?id=8VEJo-StWrkC&format=epub&output=acs4_fulfillment_token&dl_type=sample&source=gbs_api"
    },
    "pdf": {
     "isAvailable": true,
     "acsTokenLink": "http://books.google.co.jp/books/download/The_Search_for_Harry_C-sample-pdf.acsm?id=8VEJo-StWrkC&format=pdf&output=acs4_fulfillment_token&dl_type=sample&source=gbs_api"
    },
    "webReaderLink": "http://play.google.com/books/reader?id=8VEJo-StWrkC&hl=&printsec=frontcover&source=gbs_api",
    "accessViewStatus": "SAMPLE",
    "quoteSharingAllowed": false
   },
   "searchInfo": {
    "textSnippet": "They did \u003cb\u003efind\u003c/b\u003e a small cave at Spectre Mountain about three hundred feet up. It \u003cbr\u003e\nwas only about four feet high and about seven feet deep and it looked as though \u003cbr\u003e\nit may have been a burial site. It was obvious that it had been searched and \u003cbr\u003e\nruined&nbsp;..."
   }
  },

v-modelで検索フォームで入力した文字をデータとして渡す

基本的な使い方としては以下のような形があります。入力や選択をすると、即座にデータ、そしてDOMに反映されます。今回はこちらを利用して検索フォームに入力した言葉をmain.jsのmethodsに渡しています。

(参考)

html
  <p>{{message}}</p>
  <input v-model="message">
javascript
var app = new Vue({
   el: '#app'
   data:{
      message:'メッセージ'
    }
})

レスポンシブ対応

【CSS】box-sizing:border-boxの使い方|効かない時は?
以前からレスポンシブ対応に苦手意識がありましたが、今回box-sizingを利用して非常に楽に設定できるようになりました。

デザイン

materializecssBootstrapが混在してしまいました。
が、まあいっかという感じです。
このようなデザインフレームワークを使うと自動でレスポンシブになるので好きです。足りないところをプラスすればいいです。

疑問点・理解しきれていないこと

検索結果がすべて表示されないこと

APIから取得したデータはかなり多いはじなのに、1ページしか表示されません。なぜかまだ調べてみます。

var app と const vm の違い

Vue.jsを表面上しか分かった気になっていないため、このような違いに混乱します。
違いについて検索してみると、var appもあればconst vmもあり、どっちも使えるが、constは読み取り専用となり、再代入ができない等の理由がある・・・とのこと。まだ理解しておりません。

v-on と v-modelの違い

公式リファレンス:イベントハンドリングフォーム入力バインディングを参照してみると、v-onの場合はクリックしたりラジオボタンを押したりすることで、イベントを発生させる場合に使用する、v-modelの場合は、フォームに入力した文字等を非同期で反映させたいときに使用するという使い分けかなと思います。

今回参考にしたサイト

Vue.jsでQiita検索APIを使ってみる
Vue.jsとAxiosなら驚くほど簡単に作れる!外部APIを使ったWebアプリの実例
TypeScriptでNuxtアプリを作るチュートリアル【書籍検索システム】

間違っている記述やもっとこうしたほうがいいとのご意見等ありましたらコメントください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.js + Vuex + TypeScriptを使ってTodoListを作る

Vuexを使っての状態管理のやり方をいまいちイメージできていなかったので、実際に動かしながら作ってみた。

環境はvue-cli 3を使って構築しました。

詳しくは公式サイトを参照ください。

環境

node: v10.15.1
npm: 6.8.0
vue: 2.5.22
vuex: 3.0.1
typescript: 3.0.0

成果物のpackage.jsonに環境周りが書いてあるので、ご参考までに

成果物
成果物github

そもそも、vuexって何?

vuexの公式サイトには、こう書かれています。

Vuex は Vue.js アプリケーションのための 状態管理パターン + ライブラリです。

詳しくは公式サイトに詳しく載っています。

ハマったポイント

1つコンポーネントを作成して、2つ目のコンポーネントを作った時に、片方のコンポーネントしか表示されないなぁって思いました。

原因が、componentsフォルダに入っているコンポーネントファイルたちには、@Componentをつけないと、コンポーネントとして認識してくれない・・・それで少しハマりました。

src/component/TodoList.vue
<template>
    <div>
        <h3>Todo List</h3>
        <ul>
            <li v-for="todo in todos" :key="todo.id">
                <p>ID:{{todo.id}}</p>
                <p>Text:{{todo.text}}</p>
            </li>
        </ul>
    </div>
</template>
<script lang="ts">
import { Component, Vue, Prop } from 'vue-property-decorator';
import store from "@/stores/toDo";

@Component//←これをつけないと「コンポーネント」として、認識されない・・・
export default class TodoList extends Vue {
    get todos() {
        return store.getters.todos;
    }

}
</script>

少し考えたところ

今回storeファイルをインポートする形式にしたのは、今後ページを増やす際に、store自体のファイルを増やす可能性が高かったので、storesフォルダを作成して、ページごとにファイルを分けることにしました。

まとめ

ある程度vuexの流れがわかってきたので、今度はfirebaseを使って、データベース管理をどういう風にやるかを考えて、実装して、記事にしようと思います。

参考

Vue.jsとVuexでTodoListを作ってみた

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

KubernetesとNode.jsでマイクロサービスを作成する 5/8 Dockerを使ったサービス構築

第5章 Dockerを使ったサービス構築

第4章までで、Twitterライクなマイクロサービスを構成する、3つのサービスを作成してきました。
次はいよいよ、これらのサービスをマイクロサービスとして構築するのですが、いきなりKubernetesを利用する前にワンクッションはさみます。

本章では、各サービスのDockerイメージを作成し、以下のような構成で、ローカルのDocker環境でTwitterライクなサービスを起動します。

microservice-sample-docker.png

完成版のリポジトリはこちらにあるので、実装がうまくいかない場合は比較してみてください。
reireias/microservice-sample-integration

チュートリアル全体

構成

microservice-tutorial01.png

dockerとdocker-compose

まずはローカルにdockerdocker-composeがインストールされていることを確認しましょう。

docker version
Client:
 Version:           18.06.1-ce
 API version:       1.38
 Go version:        go1.10.4
 Git commit:        e68fc7a
 Built:             Fri Jan 25 14:33:54 2019
 OS/Arch:           linux/amd64
 Experimental:      false

Server:
 Engine:
  Version:          18.06.1-ce
  API version:      1.38 (minimum version 1.12)
  Go version:       go1.10.4
  Git commit:       e68fc7a
  Built:            Thu Jan 24 10:56:33 2019
  OS/Arch:          linux/amd64
  Experimental:     false
docker-compose version
docker-compose version 1.23.2, build 1110ad0
docker-py version: 3.7.0
CPython version: 3.7.2
OpenSSL version: OpenSSL 1.0.2g  1 Mar 2016

microservice-sample-integrationリポジトリの作成

ここからは、各サービスのリポジトリ内のコードを利用して、DockerイメージやKubernetesの設定を作成していくことになります。
そのためには、各サービスのコードが参照できる構成が望ましいです。

今回は下記のディレクトリ構成をとるような、microservice-sample-integrationリポジトリを作成します。

microservice-sample-integration
└── services
    ├── microservice-sample-tweet
    ├── microservice-sample-user
    └── microservice-sample-web

実際の開発現場では、このリポジトリの管理をSREチームが主導して行ったり、各サービスの開発者が協力して行ったりします。

microservice-sample-integrationという名前でリポジトリを作成し、cloneしておきましょう。

つづいて、このリポジトリ内で行う各種操作をMakefileに記述し、makeコマンドから実行できるようにします。
作成するコマンドは下記の通りです。

  • clone: 各サービスをservicesディレクトリ以下にcloneする
  • pull: 各サービスをgit pullする
  • build: 各サービスのDockerイメージのビルドを行う
  • up: 全サービスをdocker-composeを使って、ローカルのDocker環境で立ち上げる
  • down: 全サービスをdocker-composeを使って削除する
  • seed: UserサービスとTweetサービスでscripts/initialize.jsを実行する

Makefileは以下のようになります。

Makefile
WEB_REPOSITORY := https://github.com/reireias/microservice-sample-web
USER_REPOSITORY := https://github.com/reireias/microservice-sample-user
TWEET_RESPOSITORY := https://github.com/reireias/microservice-sample-tweet

clone:
    git clone $(WEB_REPOSITORY) ./services/microservice-sample-web
    git clone $(USER_REPOSITORY) ./services/microservice-sample-user
    git clone $(TWEET_RESPOSITORY) ./services/microservice-sample-tweet

pull:
    cd ./services/microservice-sample-web && git pull
    cd ./services/microservice-sample-user && git pull
    cd ./services/microservice-sample-tweet && git pull

build:
    docker-compose build

up:
    docker-compose up -d

down:
    docker-compose down

seed:
    docker-compose exec user node /app/scripts/initialize.js
    docker-compose exec tweet node /app/scripts/initialize.js

Makefileが作成できたら、make pullを実行し、各サービスをservicesディレクトリ配下へcloneします。

make clone

Dockerイメージの作成

つづいて、各サービスのDockerイメージを作成するための、Dockerファイルを実装していきます。

このファイルから生成されるDockerイメージは本番環境でも使用されます。
実際の開発現場では各サービスが責任を持って作成する場合もありますし、SREチームなど、Dockerのノウハウに長けたチームがサービス横断で管理するケースもあるでしょう。

services/microservice-sample-tweet/Dockerfile
FROM node:11.10-alpine

ENV NODE_ENV=production

WORKDIR /app
RUN apk add --no-cache curl && \
    curl -sfL https://install.goreleaser.com/github.com/tj/node-prune.sh | sh
ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.5.0/wait /wait
RUN chmod +x /wait

COPY package.json .
RUN yarn install && ./bin/node-prune
COPY . .
EXPOSE 3000
CMD /wait && yarn start
services/microservice-sample-user/Dockerfile
FROM node:11.10-alpine

ENV NODE_ENV=production

WORKDIR /app
RUN apk add --no-cache curl && \
    curl -sfL https://install.goreleaser.com/github.com/tj/node-prune.sh | sh
ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.5.0/wait /wait
RUN chmod +x /wait

COPY package.json .
RUN yarn install && ./bin/node-prune
COPY . .
EXPOSE 3000
CMD /wait && yarn start
services/microservice-sample-web/Dockerfile
FROM node:11.10-alpine as builder

ENV NODE_ENV=production
ARG GITHUB_CLIENT_ID
ARG GITHUB_CLIENT_SECRET
WORKDIR /app

RUN apk add --no-cache curl && \
    curl -sfL https://install.goreleaser.com/github.com/tj/node-prune.sh | sh

COPY package.json .
COPY yarn.lock .
RUN yarn install && ./bin/node-prune
COPY . .
RUN yarn build

FROM node:11.10-alpine

WORKDIR /app
ADD package.json ./
ADD nuxt.config.js ./

COPY --from=builder ./app/server ./server
COPY --from=builder ./app/node_modules ./node_modules
COPY --from=builder ./app/.nuxt ./.nuxt

EXPOSE 3000
CMD yarn start

Webサービスに関してはマルチステージビルドを利用して最終的なイメージのサイズを軽くするように工夫しています。

実装後、各Dockerfilecommitしておきましょう。

docker-compose.ymlの作成

上記で用意したDockerfileを、dockerコマンドを利用してビルド、コンテナの起動等やってもよいのですが、環境変数等、引数が多くなってしまうため、今回はdocker-composeを利用して一元管理します。

docker-compose.ymlに起動したいコンテナの設定を書いていくことで、一括で立ち上げたり、削除することが可能です。

今回は、Webサービス、Userサービス、Tweetサービスと、Userサービス用DB、Tweetサービス用DBの合計5つのコンテナを定義します。

docker-compose.yml
---
version: '3'
services:
  web:
    build:
      context: ./services/microservice-sample-web
      dockerfile: Dockerfile
    image: microservice_web:1.0
    ports:
      - 3000:3000
    environment:
      NUXT_HOST: 0.0.0.0
      USER_SERVICE: http://user:3000
      TWEET_SERVICE: http://tweet:3000
      GITHUB_CLIENT_ID: ${GITHUB_CLIENT_ID}
      GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET}
  user:
    build:
      context: ./services/microservice-sample-user
      dockerfile: Dockerfile
    image: microservice_user:1.0
    environment:
      MONGODB_URL: mongodb://user-db:27017/user
      MONGODB_ADMIN_NAME: root
      MONGODB_ADMIN_PASS: example
      WAIT_HOSTS: user-db:27017
    depends_on:
      - user-db
  user-db:
    image: mongo
    volumes:
      - user-db:/data/db
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: example
  tweet:
    build:
      context: ./services/microservice-sample-tweet
      dockerfile: Dockerfile
    image: microservice_tweet:1.0
    environment:
      MONGODB_URL: mongodb://tweet-db:27017/user
      MONGODB_ADMIN_NAME: root
      MONGODB_ADMIN_PASS: example
      WAIT_HOSTS: tweet-db:27017
      USER_SERVICE: http://user:3000
    depends_on:
      - tweet-db
  tweet-db:
    image: mongo
    volumes:
      - tweet-db:/data/db
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: example
volumes:
  user-db:
  tweet-db:

GitHubのOAuthのキー等はGit管理する対象ではないので、.envファイルに記述します。
docker-composeの機能により、カレントディレクトリの.envファイルが読み込まれ、docker-compose.yml中で利用することができるようになります。
値はDocker環境の場合エンドポイントがlocalhost:3000と、前章までと同じなため、前章で使用していたのと同じGitHubのOAuth設定が利用可能です。

.env
GITHUB_CLIENT_ID=xxxxxxxxxxxxxxxxxxx
GITHUB_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxx

services/.gitkeepファイルを作成しておきます。

touch services/.gitkeep

.gitignoreファイルを以下のように設定します。

.gitignore
services/*
!services/.gitkeep
.env

サービスを起動してみる

それでは、サービスを起動してみます。

# Dockerイメージをビルド
make build
# サービスの立ち上げ
make up
# ダミーデータの投入
make seed

http://localhost:3000へアクセスしてみましょう。
問題なく動作していれば本章の実装は完了になります。

第5章まとめ

本章では、各サービスのDockerfileを作成し、docker-composeを利用してマイクロサービスをローカルのDocker環境上に構築しました。

次の章ではいよいよKubernetes上でマイクロサービスを動作させてみます。

次章: 第6章 Kubernetes with minikube

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Responder + Vue.jsプロジェクト作成手順

TL;DR

最近responderというPython製の非同期処理が売りのWebフレームワークを知ったので、Vue.jsと組み合わせてプロジェクトを作成した手順を紹介します。

プロジェクト作成

Responder側

まずはディレクトリを作成してpipenvで環境を作っていきます。

$ mkdir responder-vue-sample
$ cd responder-vue-sample
$ pipenv --python 3.7

Pipfileを以下のように編集

[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true

[dev-packages]

[packages]
asgiref = "==2.3.2"
responder = "*"

[requires]
python_version = "3.7"

responder以外にasgirefPipfileにバージョンを固定して記述しています。
asgirefresponderの依存パッケージでresponderをインストールすれば自動的にインストールされるのですが、記事投稿時そのままでは3.0.0が入ってしまいVue.jsのビルド済みファイルが入るstaticディレクトリ配下のファイルを正しく読み込めないため2.3.2で固定しています。

以下のコマンドでライブラリをインストールします。

$ pipenv install

サーバープログラム

プロジェクト直下にapp.pyを作成してstatic/index.htmlを表示するだけのコードを記述します。

app.pyj
import responder

api = responder.API()

if __name__ == '__main__':
    api.add_route('/', static=True)
    api.run()

Vue.js側

まずvue-cliをグローバルにインストールします。

$ npm install -g @vue/cli

インストール後、先程作ったプロジェクト直下にVue.jsのプロジェクトを作っていきます。
カレントディレクトリの指定以外はお好みで大丈夫です。

$ vue create .
Vue CLI v3.5.1
? Generate project in current directory? (Y/n) Y
? Please pick a preset: (Use arrow keys)
❯ default (babel, eslint)
  Manually select features

Vue.jsのプロジェクトの作成ができたら、ビルドしたファイルをstaticに吐くようにpackage.jsonを編集します。

package.json
{
  "name": "responder-vue-sample",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
-    "build": "vue-cli-service build",
+    "build": "vue-cli-service build --dest static",
    "lint": "vue-cli-service lint"
  },
  "dependencies": {
    "vue": "^2.6.6"
  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "^3.5.0",
    "@vue/cli-plugin-eslint": "^3.5.0",
    "@vue/cli-service": "^3.5.0",
    "babel-eslint": "^10.0.1",
    "eslint": "^5.8.0",
    "eslint-plugin-vue": "^5.0.0",
    "vue-template-compiler": "^2.5.21"
  },
  "eslintConfig": {
    "root": true,
    "env": {
      "node": true
    },
    "extends": [
      "plugin:vue/essential",
      "eslint:recommended"
    ],
    "rules": {},
    "parserOptions": {
      "parser": "babel-eslint"
    }
  },
  "postcss": {
    "plugins": {
      "autoprefixer": {}
    }
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not ie <= 8"
  ]
}

次にベースURLを/からstaticに変更するためにvue.config.jsを作成してpublicPathを変更します。

vue.config.js
module.exports = {
  publicPath: "static"
}

最終的に以下のようなファイル構成になるかと思います。

$ tree -L 1
tree -L 1
.
├── Pipfile
├── Pipfile.lock
├── README.md
├── app.py
├── babel.config.js
├── node_modules
├── package.json
├── public
├── src
├── static
├── templates
├── vue.config.js
└── yarn.lock

ビルドと実行

では実際にビルドしてからサーバープログラムを起動してresponderからVue.jsが呼べているか確認していきます。

$ pipenv run responder build # or yarn build
$ pipenv run python app.py

これでlocalhost:5042にアクセスして以下のようにVue.jsのホーム画面が出たら終了です。お疲れ様でした。

スクリーンショット 2019-03-30 0.16.12.png

完成したものはこちら
https://github.com/KeisukeToyota/responder-vue-sample

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む