Yahoo天気をスクレイピングして予報をSlackへ通知する
はじめに
ちょっと前にYahoo天気の指数情報に洗濯や熱中症などに混じって、
ビールが追加されて話題になってました。
数ある天気予報の中でビールの指数を出しているところは見たことがなかったので、
Yahoo天気の予報をSlackに通知して毎朝確認しよう!と思ったのが今回の話。
APIを提供してくれていれば楽だったんだけど、
無さそうだったのでスクレイピングして無理やり取ってくることにする。
今回はtypescriptでサクッと作ったものをAWSに乗せて使うことにする。
TL;DR.
事前準備
コード側
今回使うライブラリなどをinstallしておく。
# 今回使うlibrart(httpリクエスト用、HTMLパーサ) $ npm install --save request jsdom # 型定義(好みで) $ npm install --save-dev @types/request @types/jsdom # その他 # TSとかeslintとかwebpackとかetc...
Slack側
ここからwebhook許可しとく
追加手順
プルダウンから通知を送りたいチャンネルを指定してAdd Incoming WebHooks integration
をクリック。
(もしかしたら日本語表記だと違うかも)
Setup Instructions
にあるWebhook URL
をコピーしておく。
これでSlack側の設定は完了
実装
実践ドメイン駆動設計をレイヤードアーキテクチャの辺りまで読んだので、
レイヤードアーキテクチャ的に実装していくことにする。
+ typescriptでDIをやるためにinversify
を入れる。
必須ではないので必要であれば追加でインストール(npm install --save inversify
)。
Interface
とりあえず叩かれる用。
受け取るパラメータも特にないので、ただapplication層を呼び出すだけ。
コード中の@injectable()
と@inject()
はinversify
用の設定。
@injectable()
はDIされる側の設定。interfaceの実装食らうに付けておく。
@inject()
はDIする側の設定。これで、interfaceを呼び出せば対応する実装クラスを引っ張ってこれる。
Application
Domain使ってるところ。
Domデータ取ってくる→欲しいデータに整形する→Slackへ通知の流れ。
Domain
ScrapingService
サイトにGet飛ばしてhtml取ってくるところ。
取ってきたデータをjsdom
を使ってパースして返す。
ConverterService
パースしたデータを使いやすいように整形するところ。
整形したり、必要なデータを取り出したりしている。
ゴリ押しに次ぐゴリ押しで実装してしまったので、 もうちょい綺麗にしたい。。。
InformSlackService
Slcakへ通知するところ。
送るデータを受け取って、Slackのwebhookで送れる形にしてあげる。
Infrastructure
HttpRequest
request
をラップしているだけ。
ここももう少し綺麗にしたいところ。
他のライブラリに変えても良い気がするし、promiseかまさなくても良いようにしたい。
inversify用設定
inversifyのGithubを参考にしながら設定を進める。
inversify.config.ts
interfaceと実装を紐づける設定。
DIコンテナ的な設定の認識。
const container = new Container(); container .bind<WeatherNewsService>(TYPES.WeatherNewsService) .to(WeatherNewsServiceImpl); container .bind<ConverterService>(TYPES.ConverterService) .to(ConverterServiceImpl); container .bind<InformSlackService>(TYPES.InformSlackService) .to(InformSlackServiceImpl); container.bind<ScrapingService>(TYPES.ScrapingService).to(ScrapingServiceImpl); container.bind<HttpRequest>(TYPES.HttpRequest).to(HttpRequestImpl); container.bind<WeatherNews>(TYPES.WeatherNews).to(WeatherNews); export { container };
inversify.type.ts
実行時に識別子が必要なので、宣言しておく。
公式にならってSymbol
を使っているが、クラスでも文字列でもOKだそう。
const TYPES = { WeatherNewsService: Symbol.for('WeatherNewsService'), ConverterService: Symbol.for('ConverterService'), InformSlackService: Symbol.for('InformSlackService'), ScrapingService: Symbol.for('ScrapingService'), HttpRequest: Symbol.for('HttpRequest'), WeatherNews: Symbol.for('WeatherNews') }; export { TYPES };
実行してみる
ここまでのコードを実行してみる。
実際に作ったものはAWSに乗せるように設定が変わっているけど、
概ね下記のようなwebpack.config
を用意すればビルドできるはず。
// output.pathに絶対パスを指定する必要があるため、pathモジュールを読み込んでおく const path = require('path'); const nodeExternals = require('webpack-node-externals'); const slsw = require('serverless-webpack'); module.exports = { mode: slsw.lib.webpack.isLocal ? 'development' : 'production', entry: slsw.lib.entries, devtool: 'source-map', target: 'node', module: { rules: [ { test: /\.ts$/, use: [ { loader: 'ts-loader' }, { loader: 'eslint-loader' } ], exclude: /node_modules/ } ] }, resolve: { extensions: ['.js', '.json', '.ts'], modules: ['node_modules'] }, output: { libraryTarget: 'commonjs', path: path.join(__dirname, '.webpack'), filename: '[name].js', }, externals: [nodeExternals()] };
下記のようなメッセージがSlackに通知されればOK。
まとめ
実装して動かしてみるところまでやった。
思ったより長くなりそうなので、今回はここまでにする。
次回serverless framework
を使ってAWSに上げるところまでやる。
それでは今回はこのへんで。
追記
参考
yaml書き方メモ
はじめに
最近設定ファイルをyamlで書くことがある今日このごろ。
毎回書き方忘れて調べてるのでまとめることにする。
JSONならわかるので比較しながら書いていく。
基本構文
JSONと同じ(はず)。
ただ、文字列を"
で囲って上げる必要はない。
囲んでも問題ないし、\n
とかの特殊文字や空白を含めるたい時は囲って上げる必要がある。
ちなみにyamlにはコメントがある。
先頭に#
を付けた行はコメント行になる。
配列
yaml
# ブロック形式 - aaa - bbb - ccc # インライン形式 [aaa, bbb, ccc]
json
["aaa", "bbb", "ccc"]
連想配列
yaml
key1: value1 key2: 100
json
{ "key1": "value1", "key2": 100 }
組み合わせ
yamlの方は自信ないかも
yaml
key1: - value1 - value2 key2: - value3: - hoge - huga - value4: - piyo - poyo key3: - value5: foo value6: bar - value7: 100 value8: 200
json
{ "key1": [ "value1", "value2" ], "key2": [ { "value3": [ "hoge", "huga" ] }, { "value4": [ "piyo", "poyo" ] } ], "key3": [ { "value5": "foo", "value6": "bar" }, { "value7": 100, "value8": 200 } ] }
まとめ
ざっくり書き方をまとめてみた。
公式サイトを見る限り他にも色々あるっぽいけど、
とりあえずこれくらい押さえておけば書けそうなので今回はここまでにしておく。
また何かあったら調べようと思う。
それでは、今回はこのへんで。
参考
NestJS事始め
はじめに
expressの書き方に疲れてspringを使おうか迷っていた今日このごろ。
A progressive Node.js framework for building efficient and scalable server-side applications, heavily inspired by Angular.
と謳っているNestJSなるフレームワークがあると聞いたので触ってみる。
TL;DR.
Install
公式のcliがあるのでサクッとインストール。
$ npm install -g @nestjs/cli
Projectの作成
$ nest new project-name > npm か yarnどっち使うか聞かれるので好みの方を選択
これでアプリの雛形が作成される。 生成されるファイルは下記の通り。
. ├── README.md ├──node_modules │ └── 省略 ├── nest-cli.json ├── nodemon-debug.json ├── nodemon.json ├── package-lock.json ├── package.json ├── src │ ├── app.controller.spec.ts │ ├── app.controller.ts │ ├── app.module.ts │ ├── app.service.ts │ └── main.ts ├── test │ ├── app.e2e-spec.ts │ └── jest-e2e.json ├── tsconfig.build.json ├── tsconfig.json └── tslint.json
とりあえず起動してみる
@nestjs/cli
は後述するが色々ファイルを生成してくれる。
してくれるが、起動してくれる機能は無いので生成されたpackage.json
に書いてあるnpm script
を使う。
port3000で起動するので、とりあえずcurlで叩いてみる。
$ npm start [Nest] 45934 - 06/05/2019, 10:08 PM [NestFactory] Starting Nest application... [Nest] 45934 - 06/05/2019, 10:08 PM [InstanceLoader] AppModule dependencies initialized +19ms [Nest] 45934 - 06/05/2019, 10:08 PM [RoutesResolver] AppController {/}: +7ms [Nest] 45934 - 06/05/2019, 10:08 PM [RouterExplorer] Mapped {/, GET} route +6ms [Nest] 45934 - 06/05/2019, 10:08 PM [NestApplication] Nest application successfully started +3ms $ curl localhost:3000/ > Hello World!
無事動いているようなので手を加えていってみる。
Controller,Serviceを追加してみる
公式サイトのDocumentを参考にしながら、新しいController,Serviceを作ってみる。
# ファイルの生成とmoduleへの追加を自動でしてくれる $ nest generate controller sample > CREATE /src/sample/sample.controller.spec.ts (493 bytes) > CREATE /src/sample/sample.controller.ts (101 bytes) > UPDATE /src/app.module.ts (397 bytes) # `g`はgenerateのエイリアス。 sは`service`のエイリアス。(controllerのエイリアスは`co`) $ nest g s sample > CREATE /src/sample/sample.service.spec.ts (460 bytes) > CREATE /src/sample/sample.service.ts (90 bytes) > UPDATE /src/app.module.ts (597 bytes)
生成されたファイル
controller
import { Controller } from '@nestjs/common'; @Controller('sample') export class SampleController {}
service
import { Injectable } from '@nestjs/common'; @Injectable() export class SampleService {}
Controller,Serviceをちょっと修正
このままでは増えただけなので、ちゃんと中身を書いていく。 とりあえずGET,POST,PUT,DELETE辺りを実装してみる。
Controller
公式サイトのControllerを参考にしながら、書いていく。
色々アノテーションを付けた。今回使って無いのもあるので、詳しくはこちらから。
import {Body, Controller, Delete, Get, HttpCode, Param, Post, Put, Req} from '@nestjs/common'; import {SampleService} from './sample.service'; @Controller('sample') export class SampleController { constructor(private readonly sampleService: SampleService) {} @Get() public getSample(): string { return this.sampleService.getSample(); } /** * パスの値を可変にしたい場合、express同様`:変数名`でOK * @param params Expressのreq.paramsと同義 */ @Get(':id') public getSamplePath(@Param() params): string { return this.sampleService.getSampleById(params.id); } /** * requestの全量は`@Req()`をつけることで取得できる * @param req Expressのreqと同義 */ @Post() @HttpCode(201) public postSample(@Req() req): any { return this.sampleService.registerSample(req.body); } /** * Putサンプル * @param params パスから受け取る * @param body Expressのreq.bodyと同義。bodyだけ取得できる */ @Put(':id') public putSample(@Param() params, @Body() body): any { return this.sampleService.updateSample(params.id, body); } /** * HttpCodeでレスポンスのステータスコードを指定できる。 * @param id 直接取得したい場合は`@Param()`の引数に変数名を指定してあげればOK */ @Delete(':id') @HttpCode(204) public deleteSample(@Param('id') id: string): void { this.sampleService.deleteSample(id); } }
Service
こちらはデフォルトとにならってメソッドを増やしただけ。
import { Injectable } from '@nestjs/common'; @Injectable() export class SampleService { public getSample(): string { return 'sample service!'; } public getSampleById(id: string): string { return `id is ${id}`; } public registerSample(param: any): any { return {message: 'OK', result: param}; } public updateSample(id: string, param: any): any { return {message: id, result: param}; } public deleteSample(id: string): void { console.log(`${id} is deleted`); } }
叩いてみる
$ curl localhost:3000/sample > sample service! $ curl localhost:3000/sample/sampleId > id is sampleId $ curl -XPOST -H "Content-Type:application/json" localhost:3000/sample -d '{"id": "sampleId", "title": "sample", "message": "sample message"}' | jq . >{ > "message": "OK", > "result": { > "id": "sampleId", > "title": "sample", > "message": "sample message" > } >} $ curl -XPUT -H "Content-Type:application/json" localhost:3000/sample/sampleId -d '{"title": "sample", "message": "sample message"}' -v | jq . >{ > "message": "sampleId", > "result": { > "title": "sample", > "message": "sample message" > } >} $ curl -XDELETE localhost:3000/sample/sampleId -v > ... >< HTTP/1.1 204 No Content >< X-Powered-By: Express >< Date: Wed, 05 Jun 2019 14:49:40 GMT >< Connection: keep-alive
HTMLを返せるようにする
HTML
まず返すHTMLファイルを作る。
ファイルは下記のような箇所に配置しておく。
. ├── README.md ├── nest-cli.json ├── nodemon-debug.json ├── nodemon.json ├── package-lock.json ├── package.json ├── src ├── test ├── tsconfig.build.json ├── tsconfig.json ├── tslint.json └── views # ここを追加 └── index.html # 追加したHTML
main.ts修正
自動生成されたmain.ts
に追記する。
これも同じく公式サイトを参考に書いていく。
import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import {NestExpressApplication} from '@nestjs/platform-express'; import {join} from 'path'; async function bootstrap() { // createの後ろに型を追加 const app = await NestFactory.create<NestExpressApplication>(AppModule); // ファイルが入っているフォルダを指定 app.useStaticAssets(join(__dirname, '..', 'views')); await app.listen(3000); } bootstrap();
動かしてみる
npm start
で起動した後、localhost:3000
にアクセス。
下記の画像の用にHTMLが返ってくればOK。
Helmetを導入する
expressでとりあえず入れとけって風潮があるhelmet
を導入してみる。
これも公式サイトにあるので参考にしてやる。
helmetインストール
$ npm install --save helmet
main.ts改修
import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import {NestExpressApplication} from '@nestjs/platform-express'; import {join} from 'path'; // import追加 import * as helmet from 'helmet'; async function bootstrap() { const app = await NestFactory.create<NestExpressApplication>(AppModule); app.useStaticAssets(join(__dirname, '..', 'views')); // ここでhelmetを使うよう修正 app.use(helmet()); await app.listen(3000); } bootstrap();
まとめ
今回はnest.jsを使ってみた。
謳っているだけあって、全般的にAngularに似てる感じ。
Controllerの書き方とかはSpringに似てるのかなと思った。
expressよりも書きやすいしデフォルトでtsだし、個人的にはとても良さげ。
多分SSRとかもできるはずなのでいずれ調べたいところ。
これからガンガン使ってみようと思う。
参考資料
Angular8アップデート内容ざっくりまとめ
はじめに
今日、Angular8がリリースされたらしい。 Mediumも更新されていたので、さっくりまとめる。
原文
アップデート方法
直近のアップデートを入れている人は、ng update @angular/cli @angular/core
でOK。
(多分Angular6くらい?)
それ以外の人はupdate.angular.ioでアップデート方法を確認できる。
Differential Loading By Default
Differential Loadingというブラウザが自身の性能に合わせて、モダンかレガシーなJavaScriptを選ぶプロセスがある。
Angularのビルドはデフォルトでモダン向けのビルド(es2015)とレガシー向けのビルド(es5)を行ってくれるようになった。
ユーザがAngular8のアプリケーションを読み込んだ時、自動で必要なファイルを取得してくれる。
ng update
でAngular8にバージョンアップするとtsconfig.json
をDifferential Loadingに対応するよう勝手にアップデートしてくれる。
CLIはtsconfig.json
のtarget
を見てDifferential Loadingが必要か判断してくれる。
target
にes2015
を設定するとモダンとレガシー、2つのバンドルを生成してくれる。
実行時にScriptタグを見て、正しい方を読み込んでくれる。
<script type="module" src="…"> // Modern JS <script nomodule src="…"> // Legacy JS
angular.ioではこれでモダンブラザ向けに40KBほど初期バンドルサイズを削減できたらしい。7〜20%くらいの割合に当たるとのこと。
Differential Loadingについて詳しくしりたい場合はこちらから。
Route Configurations use Dynamic Imports
モジュールの遅延ロードをするとき、今まではAngular独自の書き方であったが、一般的なDynamic Importの形に変わった。
ちなみにこれでVSCodeとかのサポートを受けられるらしい。
これもng update
すると勝手にアップデートしてくれる。
// 今まで {path: '/admin', loadChildren: './admin/admin.module#AdminModule'} // これから {path: `/admin`, loadChildren: () => import(`./admin/admin.module`).then(m => m.AdminModule)}
Builder APIs in the CLI
ng new
, ng generate
, ng add
, ng update
のようなBuilderAPIが増えた。
こことかここを確認すると良さそう。
最新版のAngular Fireを使えばngコマンドでFirebaseにデプロイできる。
$ ng add @angular/fire $ ng run my-app:deploy
Workspace APIs in the CLI
今まではworkspace configurationを変更するために、直接angular.json
を変更する必要があった。
8からはAPIが用意されたらしい。詳しくはこちらから。
リンクも読んだんだけど、そもそもWorkspaceが何のことかわからなかったので、なんの恩恵があるかわからなかった。
悲しいから今度調べてみる。
Web Worker Support
WebWorkerをCLIから作れるようになった。
プロジェクトに追加するには、ng generate webWorker my-worker
でOK。
一度追加してしまえば、CLIがよしなにバンドルしてくれるし分割してくれる。
詳しくからこちらから。
const worker = new Worker(`./my-worker.worker`, { type: `module` });
AngularJS Migration Improvements
AngularJSの$location service
を使う場合、LocationUpgradeModule
を提供するようになったらしい。
これにより、AngularのLocation
にシフトしてくれるらしい。
AngularJSを知らないのでなんとも言えないけど、AngularからAngularJSのアプリケーションをロードできるっぽい。
詳しくはこことかここを参照すると良い。
New Deprecation Guide
廃止予定の機能も廃止予定バージョンから2バージョン後までサポートしてくれるらしい。
Angular8でplatform-webworker
が非推奨になり8.1で廃止されるが、Angular9,10でも機能してくれるとのこと。
非推奨機能の一覧と削除予定はこちらから。
Ivy & Bazel
今回入るって言われてた気がするけど、入らなかったらしい。
続報についてはブログ(原文のMedium)で出されるとのこと。
入らなかったのはとても残念。
勘違いで一応入っているっぽい?
オプトインとのこと。
続報がブログであることは変わらなそう。
まとめ
ざっくりアップデート内容を和訳してまとめてみた。
5,6,7では正直クリティカルなアップデートはなかった気もしてる。
ただ、今回はDifferential LoadingとかWebWokerとかBuilder APIとか入れといた方が良さそうなものが多い印象。
今後もアップデートは続くだろうし積極的に追ってきたいところ。
とりあえず手元のアプリをアップデートするところから始めます。
それでは今回はこの辺で。
@ngrx/effectsを使ってみる
はじめに
昔@ngrx/store
についてまとめたから、今回は@ngrx/effects
についてまとめる。
インストールしてサクッと動かしてみるとこまで。
TL;DR.
@ngrx/effectsとは
APIアクセスなどの副作用のある処理をラップしてくれるもの。
前提条件
(なくてもいいけど、)前回の状態。
インストール
$ npm install --save @ngrx/effects
Service作成
ここは普通のAngularと同じく、API叩くServiceを作成する。
ただ、今回は 作るのがめんどくさかったから サクッと作るためObservable生成して流すことにする。
@Injectable({ providedIn: 'root' }) export class TodoService { constructor(private http: HttpClient) { } public fetchAll(): Observable<Array<string>> { return Observable.create(observable => { observable.next(['task1', 'task2', 'task3']); observable.complete(); return observable; }); } }
Action作成
サービスを取得するためのActionを作成する。
前回の状態があるなら、todo.action.ts
に追記でOK。
import { Action } from '@ngrx/store'; export enum ActionTypes { Fetch = 'TODO_FETCH', Load = 'TODO_LOAD' } export class Fetch implements Action { readonly type = ActionTypes.Fetch; }
Reducer作成
取得結果を処理する。
結果を受け取ってリストをまるっと入れ替えてあげる。
Actionと同じく todo.reducer.ts
に記載。
import { Action } from '@ngrx/store'; import { ActionTypes } from '../action/todo.action'; export const initialList: Array<string> = []; export function todoReducer(state = initialList, action: Action): Array<string> { switch (action.type) { case ActionTypes.Fetch: return [...action['payload']]; default: return state; } }
Effects作成
serviceをラップして作ってあげる様に作ってあげる。
名前はtodo.effects.ts
とする。
import {Injectable} from '@angular/core'; import {Actions, Effect, ofType} from '@ngrx/effects'; import {EMPTY} from 'rxjs'; import {catchError, map, mergeMap} from 'rxjs/operators'; import {TodoService} from '../service/todo.service'; import {ActionTypes, Fetch} from '../action/todo.action'; @Injectable() export class TodoEffects { @Effect() loadTodo$ = this.actions$ .pipe( // このイベントが発火するためのAction ofType(ActionTypes.Load), // サービス呼び出す mergeMap(() => this.todoService.fetchAll() .pipe( map(serverTodo => { // serviceの結果を元にAction発火 return new Fetch({todos: serverTodo}); }), catchError(() => EMPTY) )) ); constructor( private actions$: Actions, private todoService: TodoService ) { } }
moduleに追記
effectsを使うためにmoduleに追記する。
@NgModule({ declarations: [ AppComponent ], imports: [ // 配列形式でEffects定義。 EffectsModule.forRoot([TodoEffects]) ], providers: [TodoService], bootstrap: [AppComponent] }) export class AppModule { }
Componentに定義
componentから呼び出すための設定。
今回は初期表示時に設定した内容がほしいので、OnInit → effectsのイベント発火 → 結果をsubscribe。
という流れで設定。
@Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { public todo$: Observable<Array<string>>; public todolist: Array<string> = []; constructor( private store: Store<{ todo: Array<string>}>, private service: TodoService ) { this.todo$ = store.pipe(select('todo')); this.todo$.subscribe(res => this.todolist = res); } ngOnInit(): void { // 初期表示用にイベント発火 this.store.dispatch({ type: ActionTypes.Load }); } }
動かしてみる
ng serve
で起動。
初期表示がされてればOK!
まとめ
今回はサクッとeffectsを使ってみた。
ドキュメントのコードを丸コピしてたんだけど、
effectsからReducerに通るところでハマってた。
ちゃんと文章読むのは大事だねというところで、今回はこのへんで。
参考資料
JJUG CCC 2019 Springに参加してきました
はじめに
5月18日(土)に開催されたJJUG CCC 2019 Springに参加してきた。
今回は聞いてきたセッションについて簡単に内容と所感についてまとめてく。
参加してきたセッション
- はじめてのgRPC
- パッケージ管理してなかった既存システムに後付けでGradleを導入した
- 先行開発!Javaでクリーンアーキテクチャ -- ゼロから始める新規開発
- テストエンジニアが教えるJunitを書き始める前に考えるべきテスト
- 1400万ユーザーのWebサービスを15年運用して考える、Javaである理由
- ストラングラーパターンによるマイクロサービスマイグレーションの勘所
- マイクロサービス:4つの分割アプローチの比較
はじめてのgRPC
概要
感想
gRPCの概要、どの様に使うかという話。
gRPC自体聞いたことがなかったので、どんなものか知りたく参加してきた。
元々Googleの社内で使われていたものだとのこと。
セッション内でも言われてたけど、クライアント側で使われるのはまだ先そう(通信経路全部Http/2が必要らしい。)
便利そうだから今度触ってみたい。
パッケージ管理してなかった既存システムに後付けでGradleを導入した
概要
感想
闇に手を加えられないというのはよく聞く話だと思うけど、今回はその闇に手を入れた話。
スコープを決めてやることやらないこと決めようというとのこと。
良く良く考えたら当たり前のことだと思うけど、なかなか実践できないんだろうなと思った。
スコープとかやるやらとかはこういう話に限らないと思うので、意識していきたいところ。
先行開発!Javaでクリーンアーキテクチャ -- ゼロから始める新規開発
概要
感想
クリーンアーキテクチャの概要解説。
タイトルはJavaでとあるけど、内容はJavaに限った話じゃなかった。
クリーンアーキテクチャって名前は聞くけど、
いまいちわかってなかったから説明を聞けてよかった。
テストエンジニアが教えるJunitを書き始める前に考えるべきテスト
概要
感想
テストの考え方についての話。
テストコード書く前に考えることあるよね、曖昧な表現してると認識に差が出るよねって感じ。
テストの内容を議論するのはテスト前にできるし、やっとくと後続での工数が削減できるだろうというのは納得できた。
今後意識していけたらと思うところ。
1400万ユーザーのWebサービスを15年運用して考える、Javaである理由
概要
感想
自社サービスの開発にあたりJavaを使っている理由の説明。
昔は選択肢が少なかったからJava、その後も社内ノウハウがあったりユーザが多いからJavaとのこと。
個人的にはもっとテクニカルな理由でJavaを選んでいる理由を知りたかった。
最近だとKotlinの方が人気っぽいし、
あえてJavaを使う理由があるのかと思ってただけにちょっと残念。
ストラングラーパターンによるマイクロサービスマイグレーションの勘所
概要
感想
レガシーな環境を新しい環境にリプレイスした話。
技術的な話が多く、似たようなことをするのであればとても参考になると思った。
ただKubernetesを触ったこと無いし、よくある話なのかもしれないけどちょっと追いつけなかった。
マイクロサービス:4つの分割アプローチの比較
概要
感想
マイクロサービスを作るに辺り、システムをどの様に分割していくかという考え方の話。
ただし、考え方は実践ベースの話だけじゃなくこうした方が良いんじゃないかっていう考察込。
タイトルはマイクロサービスってなってるけど、分割基準はモジュール分けるときとかにも使えると思った。
やっぱりEricEvansの考え方は難しいというオチ。
まとめ
はじめてこういうイベントに参加してきた。
色々な話を聞けたのは楽しかったしタメになったけど、
45分っていうのは話してる人からすると全体的に短かった印象。
ただ、気楽に聞けるのはこのくらいの時間かなって思った。
今後も積極的にイベントに参加していきたい所存。
P.S.
同日に開催されていたInside Frontendの方がちょっと興味あった。
(イベント自体は知ってたけど、開催日時をよく見てなかった。。。)
Kotlin + Spring bootでRSSリーダーを作ってみる
はじめに
諸事情によりRSSリーダーを作ろうと思った今日このごろ。
具体的に言うとSlackのRSSリーダーを使って色々なものを読んでいたんだけど、
通知が即時でうるさい + ミュートにしたらしたでスマホからだと未読がどこからかわからない。
という問題というか気になることがあったので自作してみる。
作成方針としてはAPIとして立てつつ、どっかのFaaSで定期実行でも良いようにする。
技術選定理由
技術の選定理由としては、最近Java触ってオワコン言われている(と個人的に感じてる)割に書きやすいやん!と思ったのでJavaでやる。
予定だったけど、良く良く考えたらイケてるのJavaじゃなくてlombokだったのでおとなしくKotlinを使う。
Javaも8以降StreamAPIがあったり、10からは型推論もできて HogeClass hoge = new HogeClass()
みたいなの書かなくて良いし( var hoge = new HogeClass()
って書ける)だいぶ書きやすいと思う。
でも便利系はやっぱりlombokが必要でアノテーション祭りになるのがなと思ってパス。
KotlinにしたのはJava使いたい + 記法がtsに似てて多分そこまで苦労しないだろう読み。
TL;DR
前提条件
KotlinとSpringbootが動くこと。
IntelliJ入れておけば特に困ること無いと思う。(たしか。)
RSSリーダーを作っていく
今回レイヤーアーキテクチャ風に作っていくことにする。
理由としては最近DDDの本を読んだので、試したかったから。
さっそくプロジェクトを作るところから始める。
プロジェクトセッティング
今回はSpringを使うので安定のSpring Initializrスタート。
Projectを Gradle
、Languageを Kotlin
に設定。
Dependenciesに Web
を追加でダウンロード。
便利そうなライブラリのインポート
大抵のやろうと思っていることは既に先人がやっているはずなのでまずはググってみる。
どうやらJavaRssリーダーを作るには Romeというやつが良いらしい。
build.gradle
に下記を追記する。
dependences {
implementation 'com.rometools:rome:1.12.0'
}
Interface層
APIとして立てるためにサクッとコントローラーを作る。
特に変なことはしてない想定。Service呼び出して取得結果を返す。
@RestController("/v1/rss") class RssReaderController(private val rssService: RssService) { /** * Rssフィード取得 */ @GetMapping fun readRss(): List<RssModel> { return this.rssService.fetchRss() } }
Application層
ドメイン層のClassを使うようにしている。
ここらへんアーキテクチャの理解が甘くて間違ってるかもしれないけど、
方針としてはビジネスロジックは含まない + 各ビジネスロジックを使うだけ。
一応取得するURLは apprication.yaml
から読むようにした。
@Component class RssServiceImpl(private val rssConverter: RssConverter, private val rssUseCase: RssUseCase) : RssService { // 自分のはてなブログURL @Value("\${properties.url.hatena}") lateinit var url: String override fun fetchRss(): List<RssModel> { val syndFeed = rssUseCase.fetchRss(url) return this.rssConverter.toRssModel(syndFeed) } }
Domain層
UseCase
ネーミングセンスがないので、 名前が思いつかなかったので、UseCaseにした。
(幅広い名前にしちゃうと後々便利屋クラスになりそうだけど、今回は自分しか触らないのでOKとする。)
やることはInfrastructure層を呼び出して使うだけ。Rssフィードを取得するメソッドを作成。
@Service class RssUseCaseImpl(private var rssReader: RssReader): RssUseCase { override fun fetchRss(url: String): SyndFeed { return rssReader.fetchRss(url) } }
Converter
取得したRssフィードから必要なデータを取り出してデータクラスに格納する。
最大取得件数を最初はかいてたんだけどマジックナンバーイケてないし、
ここにハードコードもどうなん?ということで apprication.yaml
に切り出し。
取得するデータは、記事タイトル/記事内容(一部)/記事URLの3つ。
@Service class RssConverterImpl() : RssConverter { @Value("\${properties.count}") val count: Int = 0 override fun toRssModel(syndFeed: SyndFeed): List<RssModel> { val rssList: MutableList<RssModel> = mutableListOf() val getMax = count - 1 // 最新5件のみ取得 for (i in 0..getMax) { val entry = syndFeed.entries[i] rssList.add(RssModel(entry.title, entry.description.value, entry.link)) } return rssList } }
Infrastructure層
実際にRSSフィードを取得するクラス。
Romeの使い方はGithubのExample丸コピなので省略
@Service class RssReaderImpl: RssReader { override fun fetchRss(url: String): SyndFeed { return SyndFeedInput().build(XmlReader(URL(url))) } }
使ってみる
ここまでで動くようになったはずなので実際に使ってみる。
試しに自分のはてなブログのデータを取ってみる。
# Springの起動 $ gradlew bootRun # url叩くだけ(jqはお好みで) $ curl localhost:8080/v1/rss | jq > [ > { > "title": "Docker上でSpring bootのビルド&実行するまで", > "description": "はじめに JavaというかSpringを触っている今日このごろ。 ついでにDockerで動かそうと思ったのでその設定をしていく。 前提 Dockerが使えること。 設定方法については割愛。 環境 OS: macOS Mojave Docker: Docker for Mac v2.0たのは下記の通り。書いてないとこは何でも良いと思う。 項目 内容 Project Gradle project Language Java Spring boot 2.2.0(S…", > "url": "https://minase-program.hatenablog.com/entry/2019/03/06/233949" > }, > { > "title": "React+Redux+TypeScript事始め", > "description": "はじめに 普段はAngularを使っているので、他のフレームワークを触ってみようと思った今日このごろ。 Vueは超絶軽く触ったことがあるので今回はReact。 JSXがパッと見とっつきにくかったので後回しにしてたけど、ちゃんと向き合pm install -g create-react-app # TSを使いたいからオプション指定 $ create-react-app react-sample --types…", > "url": "https://minase-program.hatenablog.com/entry/2019/02/13/225531" > }, > ...以降続き省略
まとめ
RSSリーダーって思ったより簡単に作れる + Kotlinが結構快適。
とりあえず取得するとこまでは作ったので、次はどこかに上げることを検討することにする。
それでは今回はこの辺で。