nodemailer gmail めも
node は入っているのに npm が動かなくなった
AWS LambdaによるスクレイピングでQiita Organizationの最新記事を取得しSlackに通知するBotを作った
Organizationページの「最新の投稿」をAWS Lambda(node.js)でスクレイピングし、新しい投稿の記事のタイトルとURLをSlackに投稿するBotを作ってみました
コードはここに置いてます(大分汚いです)
https://github.com/uji/qiita-organization-scraping/blob/master/app.js
AWS Lambdaの処理の流れ
- 「最新の投稿」にある記事をスクレイピングで取得
- AWS S3にあるtxtファイルを確認し、Slack未投稿の記事の有無を確認
- 未投稿の記事をSlackに投稿
- 最新記事のタイトルをtxtファイルでAWS S3に保存
投稿済みの記事を永続化する必要があるのですが、DB使うのは大げさな気がしたのでS3にtxtで保存します
スクレイピング
AWS Lambda 用のchromium chrome-aws-lambdaを使ってスクレイピングしました
Google Cloud Functionsでも使えるっぽいですpuppeteer
と使用感はほぼ同じです
.of-ItemLink_header-title
classを取ってくると「最新の投稿」の要素を取ってこれます
constchromium=require("chrome-aws-lambda");exports.handler=async(event,context)=>{letbrowser=null;letelems=[];try{browser=awaitchromium.puppeteer.launch({args:chromium.args,defaultViewport:chromium.defaultViewport,executablePath:awaitchromium.executablePath,headless:chromium.headless,});letpage=awaitbrowser.newPage();awaitpage.goto("http://qiita.com/organizations/"+process.env.ORGANIZATION_NAME);constselector=".of-ItemLink_header-title";elems=awaitpage.$$eval(selector,es=>es.map(e=>[e.textContent,e.href]));}catch(error){returncontext.fail(error);}finally{if(browser!==null){awaitbrowser.close();}}returncontext.succeed(elems);};
Slackに通知する
@slack/bolt
または@slack/web-api
でSlack APIを叩き、取得した記事をSlackに投げます
Slack Botでメッセージを送るには、singning secret
、bot token
、 channel id
が必要になります
const{App,ExpressReceiver}=require("@slack/bolt");constreceiver=newExpressReceiver({signingSecret:process.env.SLACK_SIGNING_SECRET});constapp=newApp({token:process.env.SLACK_BOT_TOKEN,receiver:receiver});app.client.chat.postMessage({channel:process.env.SLACK_CHANNEL,text:"message",token:process.env.SLACK_BOT_TOKEN});
S3のテキストファイル読み込み、上書き
投稿済みの記事のタイトルを永続化しますaws-sdk
を使うと結構簡単にS3にアクセスできます
constAWS=require("aws-sdk");consts3=newAWS.S3({region:"ap-northeast-1"});lets3Params={Bucket:process.env.BACKET_NAME,Key:process.env.FILE_NAME};s3.getObject(s3Params,(err,data)=>{if(err)returncontext.fail(err);elselatest=data.Body.toString();});
constAWS=require("aws-sdk");consts3=newAWS.S3({region:"ap-northeast-1"});lets3Params={Bucket:process.env.BACKET_NAME,Key:process.env.FILE_NAME,Body:"タイトル"};s3.putObject(s3Params,(err)=>{if(err)returncontext.fail(err);});
AWS Lambdaの設定
ランタイムにnode.jsを指定してLambdaを作成します
まあまあ時間のかかる処理なので、基本設定からメモリ、タイムアウト時間を変更しておきます
メモリは512MB、タイムアウト時間は1分にしました
node_modulesはzipにまとめてLayerに登録します
AWS Lambda レイヤー
AWS Lambdaで動かす
コードをLambdaに書いてテストを実行してみます
Slackに通知されました
AWS Cloud Watch Eventsで定期実行
AWS LambdaのトリガーにAWS Cloud Watch Eventsを追加して定期実行されるようにします
毎朝9時に実行される設定にしました
まとめ
今回はスクレイピングで取得しましたがQiita APIを使ってユーザーごとの最新記事をとる方法もあります
そっちの方が良さそう
javascript(node.js)のsetHeaderとwriteHeadの違い
Node.js日記#2
Node.js (サーバサイド JavaScript) 事始め
はじめに
TypeScriptを覚えたいのですが、まずはNode.jsの勉強から始めた方が良さそうな感じでしたので、Node.jsの勉強を始めました。
Noda.jsのインストール
公式サイトからインストーラーをダウンロードしてインストールします。
LTS版の方が無難だと思います。
Node.jsはmacやLinuxのイメージが強かったのですが、Windowsでも問題なく動きました。
環境
OS:Windows 10 Pro 64bit
DB:SQL Server 2019(Cent OS 8 on Hyper-V)
node.js:v12.16.1
npm:v6.13.4
Editor:Visual Studio Code
作業フォルダの作成
今回は「D:\Node」を作業フォルダにしました。
Hello World
作業フォルダに以下のファイルを作成します。
varhttp=require('http');varserver=http.createServer(function(req,res){res.writeHead(200,{'Content-Type':'text/plain'});res.write('Hello World');res.end();});server.listen(3000);console.log('サーバを起動しました');
コマンドプロンプトを起動し、作業フォルダに移動します。
D:
CD Node
Node.jsを起動します。
起動方法は「node ファイル名」です
node sample01.js
問題がなければ、コマンドプロンプトに以下の様に表示されます。
サーバを起動しました
http://localhost:3000
にアクセスします
ブラウザに「Hello World」と表示されればOKです。
javascriptのfor in(オブジェクトのプロパティ名についての繰り返し)
Testable Redux ~ React と Redux のテスト戦略 ~
本記事では Redux を使用した場合の React コンポーネントに対するテスト方法を考察します。
Redux に接続された React コンポーネントは、コンポーネント内のプロパティを Redux Store の state と同期させています。 Redux に強く依存しているコンポーネントをどのように Testable にしていけば良いのでしょうか。記事のタイトルは、t-wada 先生の言葉をお借りして、Testable Reduxとつけさせていただきました。
純粋な React コンポーネントをテストする
さて、まずはじめに純粋な React のコンポーネントに対するテスト方法を振り返ってみます。Facebook に習えば、jest の公式ドキュメントでも紹介されている jest
と enzyme
を組み合わせた方法が一般的でしょう。
以下のようなシンプルなコンポーネントを例として使用します。
count
を保持する state をもち、onClick
で state を変更します。簡単のために Functional Component とし、State Hooks を使用します。後のテストのために各タグにはカスタムデータ属性(data-test)を付与しておきます。プロダクションビルドでカスタムデータ属性を取り除く方法もあります。こちらの記事にて詳細に解説されていました。
importReact,{useState}from"react";exportdefault()=>{const[count,setCount]=useState(0);return(<div><h3data-test="count">count: {count}</h3><buttondata-test="count-up"onClick={()=>setCount(count+1)}>⬆︎</button><buttondata-test="count-down"onClick={()=>setCount(count-1)}>⬇︎</button></div>);};
これに対するテストは以下のように記述できます。初期値が 0 であること、ボタンをクリックして 1 に変わることを確認します。
importReactfrom"react";import{shallow}from"enzyme";importCounterfrom"./PureReactCounter";importtestConfigurefrom"../../testConfigure";testConfigure();constsel=(id:string)=>`[data-test="${id}"]`;describe("<Counter /> コンポーネント",()=>{constComponent=shallow(<Counter/>);it("ボタンをクリックしてカウントアップする",()=>{expect(Component.find("h3").text()).toEqual("count: 0");Component.find(sel("count-up")).simulate("click");expect(Component.find("h3").text()).toEqual("count: 1");});});
なお、testConfigure
には以下を記述しておきます。enzyme を使用する場合のお約束のようなものです。enzyme の installationにて解説されています。
importEnzymefrom"enzyme";importEnzymeAdapterfrom"enzyme-adapter-react-16";exportdefault()=>Enzyme.configure({adapter:newEnzymeAdapter()});
結果は 8ms でした。このテストでは shallow()
を使用しています。Shallow レンダリングは、コンポーネントをユニットテストの範囲でテストできるように制限します。テストが子コンポーネントの動作を間接的にアサートしないようにしてくれます。コンポーネントは子コンポーネントに依存させず、常にステートレスに保つことによって Shallow レンダリングによるテストができ、高速です。
PASSsrc/components/PureReactCounter.test.tsx<Counter/>コンポーネント✓ボタンをクリックしてカウントアップする(8ms)
ちなみに、mount()
は完全な DOM レンダリングを行います。DOM API とやり取りする可能性のあるコンポーネントである場合や、より高次のコンポーネントにラップされているコンポーネントをテストする必要がある場合に最適です。ただしその一方で多くの依存関係を考慮することによりテストに要する時間は増える傾向にあります。
PASSsrc/components/PureReactCounter.test.tsx<Counter/>コンポーネントShallowレンダリング✓ボタンをクリックしてカウントアップする(8ms)Fullレンダリング✓ボタンをクリックしてカウントアップする(8ms)
結果として実行時間はほとんど変わりませんでした。これは今回実装したコンポーネントが他のコンポーネントや API などと依存していないシンプルなコンポーネントであるためです。
Redux の使用を開始する
さて、先ほど実装したコンポーネントは、count
を保持する state をもち、onClick
で state を変更していました。この count
という state を Redux の store で管理します。実装例を紹介する前に Redux のライフサイクルのおさらいをしましょう。
- View: ユーザが操作を行い、handleClick() などのイベント起動 function が実行される。
- ActionCreater: イベント起動 function は ActionCreater を通して action を生成する。
- Dispacther: action は Dispater に渡され、Reducer に流れる。
- Reducer: Reducer は action の type に応じて新しい state を返却する。
説明の粒度は荒いですが、おおまかにこのような流れで Redux による状態の管理が行われます。
さて、ディレクトリ構成は以下のようにして実装を進めていきます。
src/
├── index.tsx
├── App.tsx
├── components
│ ├── Counter.tsx
│ └── Counter.test.tsx
└── store
├── actions
│ └── counterActions.ts
├── reducers
│ └── counterReducer.ts
└── store.ts
Action (actions/counterActions.ts)
Action の定義を行います。今回はカウントアップとカウントダウンする2種類の action があるので事前に定義しておき、他のファイルから参照できるようにしておきましょう。
exportconstINCREMENT="INCREMENT";exportconstDECREMENT="DECREMENT";exportinterfaceICountReducerAction{type:typeofINCREMENT|typeofDECREMENT;}
Reducer (reducers/counterReducer.ts)
Reducer は飛んできた action の type に応じて state 返却する処理を書くのでした。INCREMENT
の場合は +1
、DECREMENT
の場合は -1
された新しい state を返却しています。
import{ICountReducerAction,INCREMENT,DECREMENT}from"../actions/counterActions";constinitialState=0;constcounterReducer=(state=initialState,action:ICountReducerAction)=>{switch(action.type){caseINCREMENT:returnstate+1;caseDECREMENT:returnstate-1;default:returnstate;}};exportdefaultcounterReducer;
Store
先に定義した reducer
を束ねて、store
を作成しましょう。今回 reducer
は1つですが、複数使用することになることも考慮して combineReducers()
を使用しています。
import{createStore}from"redux";import{combineReducers}from"redux";importcounterReducerfrom"./reducers/counterReducer";constreducer=combineReducers({count:counterReducer});exportdefaultcreateStore(reducer);
Component から Redux の store を参照する
Redux の state を View に表示できるようにコンポーネントを実装していきましょう。まず初めに純粋に Redux の store を直接参照するような実装を考えます。
ReactDOM.render()
を store
から subscribe()
します。
importReactfrom"react";importReactDOMfrom"react-dom";importAppfrom"./App";importstorefrom"./store/store";constrender=()=>ReactDOM.render(<App/>,document.getElementById("root"));render();store.subscribe(render);
コンポーネントは以下のように実装します。この場合はどうでしょう、テストは容易でしょうか。
importReactfrom"react";importstorefrom"../../store/store";import{INCREMENT,DECREMENT}from"../../store/actions/counterActions";exportdefault()=>{constcount=store.getState().count;// store から直接参照するreturn(<div><h3data-test="count">count: {count}</h3><buttononClick={()=>store.dispatch({type:INCREMENT})}>⬆︎</button><buttononClick={()=>store.dispatch({type:DECREMENT})}>⬇︎</button></div>);};
テストコードを振り返ってみましょう。このままでは Redux の store を参照ができないため、ボタンをクリックした後に発火するはずの Reducer が起動しません。結果として count は 0 のままです。
constsel=(id:string)=>`[data-test="${id}"]`;describe("<Counter /> コンポーネント",()=>{describe("Shallowレンダリング",()=>{constComponent=shallow(<Counter/>);it("ボタンをクリックしてカウントアップする",()=>{expect(Component.find("h3").text()).toEqual("count: 0");Component.find(sel("count-up")).simulate("click");// Redux store の state が変更されないexpect(Component.find("h3").text()).toEqual("count: 1");// 0 のまま});});describe("Fullレンダリング",()=>{constComponent=mount(<Counter/>);it("ボタンをクリックしてカウントアップする",()=>{expect(Component.find("h3").text()).toEqual("count: 0");Component.find(sel("count-up")).simulate("click");expect(Component.find("h3").text()).toEqual("count: 1");});});});
テスト結果(クリックして開く)
FAIL src/components/DirectAccessReduxStore.test.tsx
<Counter /> コンポーネント
Shallowレンダリング
✕ ボタンをクリックしてカウントアップする (11ms)
Fullレンダリング
✕ ボタンをクリックしてカウントアップする (6ms)
● <Counter /> コンポーネント › Shallowレンダリング › ボタンをクリックしてカウントアップする
expect(received).toEqual(expected) // deep equality
Expected: "count: 1"
Received: "count: 0"
13 | expect(Component.find("h3").text()).toEqual("count: 0");
14 | Component.find(sel("count-up")).simulate("click");
> 15 | expect(Component.find("h3").text()).toEqual("count: 1");
| ^
16 | });
17 | });
18 | describe("Fullレンダリング", () => {
at Object.<anonymous> (src/components/DirectAccessReduxStore.test.tsx:15:43)
● <Counter /> コンポーネント › Fullレンダリング › ボタンをクリックしてカウントアップする
expect(received).toEqual(expected) // deep equality
Expected: "count: 1"
Received: "count: 0"
21 | expect(Component.find("h3").text()).toEqual("count: 0");
22 | Component.find(sel("count-up")).simulate("click");
> 23 | expect(Component.find("h3").text()).toEqual("count: 1");
| ^
24 | });
25 | });
26 | });
at Object.<anonymous> (src/components/DirectAccessReduxStore.test.tsx:23:43)
Redux と React のコンポーネントが強く依存する関係になっており、React のコンポーネント単体としてテストしづらくなっています。この構成を変えていきましょう。まずは React と Redux の間の依存関係を切り離す方法を考えます。
react-redux
の connect()
を使用して React と Redux の依存を引き剥がず
最も代表的な実装パターンとして react-reduxの connect()
を使用する方法が挙げられます。これは、コンポーネントをマウントする時点で子コンポーネントや依存関係にある構成要素を考える必要がなく、シンプルです。
Counter コンポーネントと Redux store が強い依存関係にある実装から、なるべく疎結合になるように配慮します。いわゆる DI (Dependency Injection) の考え方にしたがって、Counter コンポーネントの store を外から props として注入できるように変更します。ただ単に props によって注入するだけではなく、Redux store との接合は connect()
によって行います。詳細な手続きは公式チュートリアルを参照しましょう。このようなコンポーネントを HOC (Higher Order Component) と呼びます。詳細はこちらの記事が参考になります。
importReactfrom"react";import{connect}from"react-redux";import{Dispatch}from"redux";import{INCREMENT,DECREMENT}from"../../store/actions/counterActions";interfaceICounterState{counter:number;}interfaceICounterProps{count:number;increment:any;decrement:any;}// props として store を流し込んで DI できるるようにする。exportconstCounter=(store:ICounterProps)=>{const{count,increment,decrement}=store;return(<div><divdata-testid="count">count: {count}</div><buttononClick={increment}>⬆︎</button><buttononClick={decrement}>⬇︎</button></div>);};constmapStateToProps=(state:ICounterState)=>({count:state.counter});constmapDespatchToProps=(dispatch:Dispatch)=>({increment:()=>dispatch({type:INCREMENT}),decrement:()=>dispatch({type:DECREMENT})});// 第一引数の mapStateToProps は component に渡す props を制御する// 第二引数の mapDespatchToProps は reducer を呼び出して、redux で管理している state を更新する// Counter は取得したデータを props として扱いたい component を指定するexportdefaultconnect(mapStateToProps,mapDespatchToProps)(Counter);
さて、ここまでできれば勝てる気がしてきました。テストを書きます。
importReactfrom"react";import{mount,shallow}from"enzyme";import{Provider}from"react-redux";import{createStore}from"redux";importsinonfrom"sinon";importConnectedCounter,{Counter}from"./ReactRedux";import{reducer}from"../../store/store";importtestConfigurefrom"../../testConfigure";testConfigure();constsel=(id:string)=>`[data-test="${id}"]`;describe("<Counter /> コンポーネント",()=>{describe("Shallowレンダリング",()=>{constprops={count:0,increment:sinon.spy(),// カウントアップするボタンを押した挙動を確認するためにスパイを差し込むdecrement:sinon.spy()};constshallowComponent=shallow(<Counter{...props}/>);it("ボタンをクリックしてカウントアップする",()=>{expect(shallowComponent.find("h3").text()).toEqual("count: 0");shallowComponent.find(sel("count-up")).simulate("click");expect(props.increment).toHaveProperty("callCount",1);});});describe("Fullレンダリング",()=>{constgetWrapper=(mockStore=createStore(reducer,{count:0}))=>mount(<Providerstore={mockStore}><ConnectedCounter/></Provider>);it("ボタンをクリックしてカウントアップする",()=>{constwrapper=getWrapper();expect(wrapper.find("h3").text()).toEqual("count: 0");wrapper.find(sel("count-up")).simulate("click");expect(wrapper.find("h3").text()).toEqual("count: 1");});});});
上記のテストでは、Shallow レンダリングと Full レンダリングの2つの方法でテストを記述しました。
Shallow レンダリングでは Redux の store を含めず、コンポーネント単体でテストを行います。そのため、カウントアップするボタンを押した際のハンドラ関数は simon.spy()
を使用してスパイを差し込んで挙動を確認します。
一方で、Full レンダリングの場合は Redux の store だけをモックとして作成し、<Provider>
の store に DI します。こちらの手法の方が Redux の store を含めたテストにはなるのですが、実行時間が増える傾向にあるため注意が必要です。ちなみに実行時間を比較すると以下のようになります。
PASSsrc/components/container/ReactRedux.test.tsx<Counter/>コンポーネントShallowレンダリング✓ボタンをクリックしてカウントアップする(9ms)Fullレンダリング✓ボタンをクリックしてカウントアップする(44ms)
mount するのはインテグレーションテストだユニットテストではない、shallow の方が高速だ、などの論争があります。個人的な意見ですが、チーム開発を行う上ではシンプルかつ可読性を維持したテストコードを書いた方がむしろチームとしてアジリティが上がるのではないかと考えています。学習コストも高いですしね。
それでもプロダクトコードの量が大きくなるにつれて、テストの実行時間の増加によりアジリティが下がるケースもあります。shallow レンダリングと full レンダリングの実行時間の差は ms 程度ですが、塵も積もれば山となるということです。テストの方針を決めるのは難しいですね。
Redux Hooks を使用してコード量を減らす
怠惰で傲慢な我々は、現状に満足することはありません。上に挙げた実装は React と Redux の依存関係を引き剥がすことで、Shallow レンダリングによるテストが実現できるようになりました。その一方で少し複雑な実装をしなければならないように感じます。この章では Redux Hooksを使用して簡単に記述できる方法をご紹介します。
React の Hooks API は、Functional コンポーネントに対してローカルコンポーネントの state を使用できるようになる優秀な機能です。react-redux
でも、既存の connect()
を使用して実装された HOC (Higher Order Component) の代わりとして Hooks API を提供するようになりました。これを使用すると、前章の実装のようにコンポーネントをにラップして HOC を作るような面倒な作業は必要ありません。Redux store に subscribe して action を dispatch できます。
導入は非常に簡単です。useSelector()
と useDispatch()
を使用しましょう。
importReactfrom"react";import{useSelector,useDispatch}from"react-redux";import{INCREMENT,DECREMENT}from"../../store/actions/counterActions";exportdefault()=>{constcount=useSelector((state:any)=>state.count);constdispatch=useDispatch();constincrement=()=>{dispatch({type:INCREMENT})};constdecrement=()=>{dispatch({type:DECREMENT})};return(<div><h3>count: {count}</h3><buttondata-test="count-up"onClick={increment}>⬆︎</button><buttondata-test="count-down"onClick={decrement}>⬇︎</button></div>);};
コードがとても短くなりました。かなりスマートです。あれ? でもおかしいですね、少し振り返ってみましょう。connect()
を使用して HOC を作ったときには純粋な React コンポーネントと Redux との依存関係を引き剥がすことに成功していました。今回、Redux Hooks を使用した実装では、また依存関係が強くなってしまいました。これでは Full レンダリングを行うようなテストしか記述できません。
describe("<Counter /> コンポーネント",()=>{describe("Fullレンダリング",()=>{constgetWrapper=(mockStore=createStore(reducer,{count:0}))=>mount(<Providerstore={mockStore}><ConnectedCounter/></Provider>);it("ボタンをクリックしてカウントアップする",()=>{constwrapper=getWrapper();expect(wrapper.find("h3").text()).toEqual("count: 0");wrapper.find(sel("count-up")).simulate("click");expect(wrapper.find("h3").text()).toEqual("count: 1");});});});
Redux Hooks でも Redux と React を分離する
基本的な考え方は今までと同様です。Redux と接続する部分を DI できるようにすれば OK です。useSelector()
と useDispatch()
を使用している箇所を外に出してやりましょう。
import{useSelector,useDispatch}from"react-redux";import{IRootState}from"../../store/store";import{INCREMENT,DECREMENT}from"../../store/actions/counterActions";import{Dispatch}from"redux";interfaceICounterProps{count:number;increment:Dispatch;decrement:Dispatch;}exportconstCounter=(props:ICounterProps)=>{const{count,increment,decrement}=props;return(<div><h3>count: {count}</h3><buttondata-test="count-up"onClick={increment}>⬆︎</button><buttondata-test="count-down"onClick={decrement}>⬇︎</button></div>);};exportdefault(props:any)=>{constcount=useSelector<IRootState>(state=>state.count);constdispatch=useDispatch();const_props={count,increment:()=>dispatch({type:INCREMENT}),decrement:()=>dispatch({type:DECREMENT}),...props};return<Counter{..._props}/>;};
当然といえば当然ですが、テストコードは react-redux
の connect()
を使用した場合の実装と同じになります。
テストコード(クリックして開く)
constsel=(id:string)=>`[data-test="${id}"]`;describe("<Counter /> コンポーネント",()=>{describe("Shallowレンダリング",()=>{constprops={count:0,increment:sinon.spy(),decrement:sinon.spy()};constshallowComponent=shallow(<Counter{...props}/>);it("ボタンをクリックしてカウントアップする",()=>{expect(shallowComponent.find("h3").text()).toEqual("count: 0");shallowComponent.find(sel("count-up")).simulate("click");expect(props.increment).toHaveProperty("callCount",1);});});describe("Fullレンダリング",()=>{constgetWrapper=(mockStore=createStore(reducer,{count:0}))=>mount(<Providerstore={mockStore}><ConnectedCounter/></Provider>);it("ボタンをクリックしてカウントアップする",()=>{constwrapper=getWrapper();expect(wrapper.find("h3").text()).toEqual("count: 0");wrapper.find(sel("count-up")).simulate("click");expect(wrapper.find("h3").text()).toEqual("count: 1");});});});
さいごに
結局のところ、どのアプローチを採用した場合も実際の Redux store を使用した React コンポーネントのテストは高速です。Redux の設計は、Action、Reducer、State のそれぞれが互いに分離される方法のため、テストに非常に適しています。
このように Dependency Injection しやすい設計のもと Redux が作られているので、このようなシンプルな構造を取ることができました。今回はご紹介できませんでしたが、Redux Sagaや Redux Thunkを使用した場合のアーキテクチャでも同様にテストはシンプルに記述できます。React をとりまくエコシステムは素晴らしいですね。
face-api.jsでニコラ○・ケ○ジになる
顔認識がjsだけでもそれなりに動くのを今更知ったので触ってみた。
顔晒すのに抵抗感がある古い人間なので文中画像少なめです。
成果物
Git: https://github.com/engabesi/face-nicolas
GithubPages: https://engabesi.github.io/face-nicolas/
やること
- expressでlocalhostを建てる
- face-api.jsで顔の矩形を取る
- 顔に画像を被せる
- GithubPagesにdeploy
face-api.js
今回顔認識に使うライブラリはこちら
https://github.com/justadudewhohacks/face-api.js
jsだけでそれなりの精度とパフォーマンスを出せます。
複数人も認識可能。
ランドマークや表情、顔認証も出来ます。
demoはこちら
https://justadudewhohacks.github.io/face-api.js/face_and_landmark_detection/
注意点
face-api.js
はWebGLを使用しています。
その為ブラウザの設定でハードウェアアクセラレーションをオフにしている場合上手く動作しない可能性があります。
expressでlocalサーバーを建てる
android等でも気軽にテストするためにまずはlocalhostで動作するようにします。
後にgithubPagesも利用したいため、以下の構成にします。
root/
├─ app.js
└─ docs/
├─ index.html
├─ images/
└─ js/
├─ index.js
└─ lib/
├─ face-api.min.js
└─ models/
├─ tiny_face_detector_model-shard1
└─ tiny_face_detector_model-weights_manifest.json
face-api.min.js
, 及びmodels内ファイルは以下のrepoから取ってきます
https://github.com/justadudewhohacks/face-api.js/tree/master/dist
https://github.com/justadudewhohacks/face-api.js/tree/master/weights
expressを導入します。
yarn init -y
yarn add express
<!DOCTYPE html><htmllang="ja"><head><metacharset="UTF-8"/><metaname="viewport"content="width=device-width, initial-scale=1.0"/><title>Document</title><script defersrc="./js/lib/face-api.min.js"></script><style>body{margin:0;padding:0;width:100vw;height:100vh;display:flex;justify-content:center;align-items:center;}canvas{position:absolute;}</style></head><body><videoid="video"width="720"height="560"autoplaymuted></video><script defersrc="js/index.js"></script></body></html>
constexpress=require("express");constapp=express();constpath=require("path");app.listen(8080,()=>{console.log("Running at Port 8080...");});app.use(express.static(path.join(__dirname,"docs")));app.use((req,res)=>res.sendStatus(404));
これでshellにnode app.js
と打ち、http://localhost:8080/にアクセスするとページが開きます(今は真っ白)
webcamera設定
conststartVideo=asyncvideo=>{try{constconstraints={audio:false,video:{}};conststream=awaitnavigator.mediaDevices.getUserMedia(constraints);video.srcObject=stream;}catch(error){console.error(error);}};(async()=>{constvideo=document.querySelector("video");awaitstartVideo(video);})();
これでlocalhostを開くとwebcameraの映像が取れているはずです。
顔の矩形を取る
まずmodelを読み込みます。
+constloadModels=async()=>{+awaitPromise.all([+faceapi.nets.tinyFaceDetector.loadFromUri(`/js/lib/models`)+]);+};(async()=>{constvideo=document.querySelector("video");+awaitloadModels();awaitstartVideo(video);})();
次に顔を認識して矩形描画処理を追加します。
(async()=>{constvideo=document.querySelector("video");awaitloadModels();awaitstartVideo(video);// --- ADD ---video.addEventListener("play",()=>{// overlay canvas作成constcanvas=faceapi.createCanvasFromMedia(video);document.body.append(canvas);constdisplaySize={width:video.width,height:video.height};faceapi.matchDimensions(canvas,displaySize);consttinyFaceDetectorOption={// default 416inputSize:224,// default 0.5scoreThreshold:0.5};setInterval(async()=>{constresults=awaitfaceapi.detectAllFaces(video,newfaceapi.TinyFaceDetectorOptions(tinyFaceDetectorOption));if(results.length<=0)return;// 検出結果をcanvasのサイズにリサイズconstresizedResults=faceapi.resizeResults(results,displaySize);// canvasの内容をクリアcanvas.getContext("2d").clearRect(0,0,canvas.width,canvas.height);// 矩形描画faceapi.draw.drawDetections(canvas,resizedResults);},100);});// --- ADD ---})();
これでscoreが記載された矩形が顔に被さって表示されるようになります。
顔に画像を被せる
検出結果から座標を取ってその位置に画像を被せてみます。
(async()=>{constvideo=document.querySelector("video");+// 画像セットアップ+constimage=newImage();+image.src=`/images/cage_neutral.png`;awaitloadModels();awaitstartVideo(video);video.addEventListener("play",()=>{constcanvas=faceapi.createCanvasFromMedia(video);document.body.append(canvas);constdisplaySize={width:video.width,height:video.height};faceapi.matchDimensions(canvas,displaySize);consttinyFaceDetectorOption={// default 416inputSize:224,// default 0.5scoreThreshold:0.5};setInterval(async()=>{constresults=awaitfaceapi.detectAllFaces(video,newfaceapi.TinyFaceDetectorOptions(tinyFaceDetectorOption));if(results.length<=0)return;constresizedResults=faceapi.resizeResults(results,displaySize);canvas.getContext("2d").clearRect(0,0,canvas.width,canvas.height);-// faceapi.draw.drawDetections(canvas, resizedResults);+resizedResults.forEach(detection=>{+// 矩形のtopはデコあたりなので調整+constmarginVal=0.4;+// 矩形の情報はdetection.boxに格納されている+constwidth=detection.box.width;+constheight=detection.box.height*(1.0+marginVal);+constx=detection.box.x;+consty=detection.box.y-detection.box.height*marginVal;+canvas.getContext("2d").drawImage(image,x,y,width,height);+});},100);});})();
これで自分の顔に画像を貼り付けることが出来ました。
他にもmodelやdetection時に.with~~
とするだけで表情やランドマーク等の情報を取れます。
詳しくは公式README参照。自分のgitにあげている物にも画像調達に飽きて中途半端になっていますが表情を取るコードも記載してあります。
GithubPages
コードをrepositoryに上げたら
repository > settings > Options > GithubPages > Sourceをmaster branch /docs folder
にすればGithubPagesにデプロイすることが出来ます。
だから、ディレクトリ名をdocsにする必要があったんですね。
注意点
上記コードでは画像のpathを直打ちしています。github.io
にデプロイする場合は問題ありませんが、github.io/SUB_REPO/
にデプロイした場合、ルートURLがgithub.io
扱いとなり、SUB_REPOが飛ばされてしまってpathがおかしくなってしまいます。jekyll
の使用をgithubは推奨していますが試して遊ぶだけの場合ちょっと面倒です。
暫定対策としてpath前に直接SUB_REPO名をくっつけてあげると一応動きます。
まとめ
たった数分でブラウザ完結の顔認識ができるなんて素晴らしい世の中になりました。
現状のコードだとスマートフォンで見た場合矩形がものすごく横長になってしまっているのでその辺の対応をしだすとヘビーになるかもしれませんがちょっと遊んで見るぐらいだと非常に有用だと思います。
他にもclmtrackr.js
やpico.js
、更にはChromeの機能だけでできるShape Detection API
等様々なライブラリがありますので是非触って遊んでみてください。
同期的に複数のファイルをform-dataを使って送信するサンプル
こんにちは、wattak777です。
一ファイルを送信する、というサンプルは幾つかあるのですが、requestとform-dataを組み合わせて送る場合、複数ファイルの場合、例えばforループで送るようにしても1つ目、2つ目、3つ目と送りきる前にどんどん送ってしまうため同期的に送ることが出来ません。
なので、ちょっとサンプルを作ってみました。
サーバー側はmulterを使った以下のサンプル。
varexpress=require('express');varapp=express();varmulter=require('multer');app.post('/file_upload',multer({dest:ファイルを置くパス}).single('my_file'),function(req,res){console.log(req.file.path,req.file.originalname);res.sendStatus(200);});varserver=app.listen(12345,function(){console.log("listening at port %s",server.address().port);});
で、本題のクライアント側は以下の実装。
request-promiseを使って同期的に送るようにしました。
constfs=require('fs');constrequest=require('request-promise');constFileNameList=['test1.bin','test2.bin','test3.bin'];varFileNameIndex=0;varreturnCode=httpPost();functionhttpPost(){constFormData={my_file:{value:fs.createReadStream(ファイルのパス+FileNameList[FileNameIndex]),options:{filename:FileNameList[FileNameIndex],contentType:'application/octet-stream'}}}constoptions={uri:"http://サーバーのIPアドレス:12345/file_upload",formData:FormData,method:'post',headers:{'Content-Type':'multipart/form-data'}}varresponse=request(options).then(function(body){console.log('then :'+body);onEnd();}).catch(function(err){console.log('catch error :'+err);});returnresponse.statusCode;}functiononEnd(){console.log('Index '+FileNameIndex+' is finish.');FileNameIndex=FileNameIndex+1;if(FileNameIndex>=FileNameList.length){console.log('End Operation.');}else{varres=httpPost();}}console.log('Start Operation.');
とやると、クライアント側の表示は以下のようになります。
$ node client.js
Start Operation.
then :OK
Index 0 is finish.
then :OK
Index 1 is finish.
then :OK
Index 2 is finish.
End Operation.
node.jsからMongoDBに接続してDocumentを登録する。
はじめに
node.jsからMongoDBをインストールして、接続しようとしたところ、最初の接続で少し躓いたので、まとめる。
環境
- Windows10 Pro 64bit
- node.js v12.14.1
- mongodb v6.13.4
- MongoDB 4.2.3
環境構築
node.jsにmongodbをインストール
以下コマンドを実行npm install mongodb
MongoDBをインストールする。
公式サイトからWindows用のインストーラをダウンロードしてインストールする。
実行して「次へ」ボタン押していく。
インストール後に環境変数にPathを追加する。C:\Program Files\MongoDB\Server\4.2\bin
MongoDB側の準備
MongoDB Compass Communityを使用して、取得したいdatabaseとDocumentを作成しておく。
左下のボタンで押下すると、Create Databaseのダイアログが出るので、入力するだけ。
node.js側のコード
以上で準備完了。実際のサンプルコードは以下。
コメントアウト部分は旧VersionのMongoDBの記載方法です。
ネットで見つけた旧Versionの記載方法だとTypeError: db.collection is not a function
のエラーになりはまる。
constMongoClient=require("mongodb").MongoClient;constdburl="mongodb://localhost:27017/";// const dburl = "mongodb://localhost:27017/myDatabase";MongoClient.connect(dburl,(error,client)=>{constcollection=client.db('myDatabase').collection('myCollection');// const collection = client.collection('myCollection');collection.insertOne({_id:1,path:"test"}).then(()=>console.log("success")).catch(err=>console.log(err));});
最後に
MongoDB以外にも、Electronもバージョンによって、大きく記述方法が変わっており、ネットで調べたコードでそのまま動かしてもうまく動かず躓くことが多い。。。
公式サイト読めばいいのだけど、時間かかるしで、少しジレンマを感じる今日この頃。
node.jsインストール直後の状態でpackage.jsonをアップデートする方法 ~npmグローバルインストール一切不要~
package.jsonを更新する場合、npm-check-updatesをグローバルインストールしてncu -u
とやることが多いです。ただ、nodebrewなどのnodeバージョン管理システムを使っている場合、node.jsの新バージョンがリリースされるたびにグローバルインストールをやり直す必要があります。この問題、何とかならないでしょうか。実はnpm-check-updatesをnpx経由で呼べば、node.jsインストール直後の状態でもpackage.jsonを更新することができます。
package.jsonを更新する
npx npm-check-updates -u
npm install
解説
npx経由でnpm-check-updatesを呼び出しているだけです。試してみましたが、npx ncu -u
とは出来ませんでした。
注意点
環境にもよりますが、上記手順はnpx npm-check-updates -u
を実行するたびにnpm-check-updatesをローカル環境のインストールします。連続してnpm-check-updatesを呼びたい場合は、npx経由で呼び出すことはお勧めしません。自分の場合は上記コマンドを呼び出すのは1週間に1回程度なので、時間ロスについてはそこまで気にしていません。
Nuxt.jsの始め方
Nuxt.jsを始めたい方に向けて、導入の仕方をまとめます。
ライフサイクルなど細かい内容は公式ドキュメントをご確認ください。
前提条件
- npmの5.2.0以降のバージョンがインストールされていること
インストール
プロジェクトディレクトリの作成
$ mkdir test&&cd$_
Nuxt.jsフレームワークのインストール
$ npx create-nuxt-app
開発
開発サーバーを起動
$ npm run dev
デプロイ
SSRモードでサーバーを起動
$ npm run build && npm run start
静的HTML生成
$ npm run generate
その他
pug/stylusの導入
pug
pugのモジュールをインストール
$ npm i --save pug pug-loader pug-plain-loader
全*.vueファイルのtemplate
タグにlang="pug"
を追加する
<!-- <template> --><templatelang="pug">
stylus
stylusのモジュールをインストール
$ npm i --save stylus stylus-loader
全*.vueファイルのstyle
タグにlang="stylus"
を追加する
ここの内容を他のスタイルに影響させたくない場合はscoped
も追加する
/* <style> */<stylelang="stylus"scoped>
storeの導入
データ定義
store用のファイルを作成
$ touch store/index.js
store/index.js
保持しておきたいデータをstate内にcounter
という名前で定義
state内のcounter
の数値を1ずつ増やすincrement
メソッドを定義
exportconststate=()=>({counter:0})exportconstmutations={increment(state){state.counter++}}
データへのアクセス
state内のcounterの値を取得
this.$store.state.counter
出力結果
0
データの変更
mutations内のincrementメソッドを実行
this.$store.commit('increment')
transitionタグ
html(pug)の定義
transition
タグを追加して、子要素はv-if
などで存在確認をさせる
transition
p(v-if="show") Hello world!
css(stylus)の定義
v-enter
とv-leave-to
で最初(出現時)と最後(消失時)のstyle
を定義
そのスタイルにどういうtransition
をかけるかv-enter-active
とv-leave-active
で定義
.v-enter-active,.v-leave-activetransitionopacity.5s.v-enter,.v-leave-toopacity0
ページ遷移transition
html(pug)の定義
nuxt-link
タグを追加する
nuxt-link(to="/test/") go to test!
css(stylus)の定義
page-enter
とpage-leave-to
で最初(出現時)と最後(消失時)のstyle
を定義
そのスタイルにどういうtransition
をかけるかpage-enter-active
とpage-leave-active
で定義
.page-enter-active,.page-leave-activetransitionopacity.5s.page-enter,.page-leave-toopacity0
ページ読み込み時のLoading表示
Loading用のファイルを作成
$ mkdir components/Loading &&touch components/Loading/index.vue
components/Loading/index.vue
<templatelang="pug">
.loading-page(v-if="loading")
p Loading...
</template><script>exportdefault{data:()=>({loading:false}),methods:{start(){this.loading=true},finish(){setTimeout(()=>{this.loading=false},3000)}}}</script><style lang="stylus"scoped></style>
nuxt.config.js
loadingの記述を追加
module.exports={loading:'~/components/Loading/index.vue'// 追記}
テンプレートレイアウト変更
テンプレート用のファイルを作成
$ touch layouts/another.vue
layouts/another.vue
<templatelang="pug">
.l-another
nuxt
</template><script>exportdefault{}</script><style lang="stylus"scoped></style>
*.vue
layoutのカスタム読み込みをする記述を追加
exportdefault{layout:'another',// 追記}
エラーページ
エラー用のファイルを作成
$ touch layouts/error.vue
layouts/error.vue
<templatelang="pug">
.container
h1(v-if="error.statusCode === 404") ページが見つかりません
h1(v-else) エラーが発生しました
nuxt-link(to="/") ホーム
</template><script>exportdefault{props:{error:{type:Object,default:null}}}</script><style lang="stylus"scoped></style>
コンソールの削除
nuxt.config.js
build内にdrop_consoleの記述を追加
module.exports={build:{terser:{terserOptions:{compress:{drop_console:true}}}}}
作業ディレクトリをまとめる
作業ディレクトリの作成、移動
$ mkdir src &&mv assets components layouts pages plugins static store middleware src/
nuxt.config.js
srcDirの記述を追加する
module.exports={srcDir:'src/',// 追記}
参考文献
この記事は以下の情報を参考にして執筆しました。
javascriptでcontent-typeを自動で取得する方法
content-typeを自動で取得する方法を探した結果、mime-types
を使うことにしました。
前提条件
- npmがインストールされていること
インストール
$ npm i --save mime-types
使い方
設定
touch
コマンドでファイルを作成します。
$ touch test.js
test.js
mime-typesモジュールの読み込みと、引数にパスを入れてメソッドを使用します。
constmime=require('mime-types')console.log(mime.lookup('test.json'))console.log(mime.contentType('test.json'))
実行
node
コマンドでtest.jsを実行します。
$ node test
出力結果
application/json
application/json;charset=utf-8
参考文献
この記事は以下の情報を参考にして執筆しました。
Text-to-Speech APIと関連ライブラリまとめ
Cloud Text-to-Speech
後述するライブラリの大元となるGoogleのAPI。テキストを音声に変換できる。
APIを有効化してAPIキーを取得すれば、スクリプト実行で音声ファイルを作成できる。
ドキュメントの「クイックスタート」にそえば、好きな言語で簡単な音声ファイルを作成することができる。
https://cloud.google.com/text-to-speech/docs/quickstart-client-libraries?hl=ja
google-tts-api
https://www.npmjs.com/package/google-tts-api
Google-Text-to-Speach-APIのnode.js版。
このライブラリだけでAPIの有効化・キー取得などを行うことなく音声ファイルの作成ができる。
しかし一度に音声化できるのは200文字まで。
google-home-notifer
https://github.com/noelportugal/google-home-notifier
このライブラリ内にgoogle-tts-apiが内包されている。
音声ファイルを作成し、Google Homeにテキストを読み上げさせることができる。
おまけ:読み上げ音声の特徴
実際に作成した音声ファイルを聞いてみて気づいた音声の特徴を簡単に。
言語=日本語の場合、
* 英単語:日本語読みで発音される
* 読み上げ時のブレス:句読点、カギカッコ、カンマなど。空白はブレスにならない。
Windowsでのnpm installの使い方
サマリ
- npm installでパッケージをインストールする際「-g」オプションの有無による違い
- npm install -gによる資材の配置場所の変更方
- npm installでインストールされたパッケージの確認方法
- npm installでインストールされたパッケージの削除方法
目的
node.jsを学習する際、npm installの使い方がわからず挫折しそうになった。
原因として、各記事で「-g」オプションの有無にばらつきがあり、
「-g」オプションの有無による具体的な違いやnpm install実行後の確認方法がまとまって説明されている記事がなかなか見つからなかった。
同じことで困った人の参考として、自分の実行例と合わせて説明を記載する。
本文
1. npm installコマンドの効果
node.jsで利用するパッケージをダウンロードできる。
ダウンロードしたパッケージはrequireで取得・利用できるようになる。
例)expressをインストールした場合var express = require('express')
2. 「-g」オプションの有無による違い
「-g」オプションが無い場合
npm install
を実行したフォルダ配下でのみパッケージを利用可能。「-g」オプションが有る場合
npm install
を実行したフォルダ配下以外からでもパッケージを利用可能。
3. 「-g」オプションの有無によるダウンロード資材の配置場所の違い
「-g」オプションが無い場合
npm install
を実行したフォルダ配下のnode_modules
フォルダ。「-g」オプションが有る場合
npm config list
コマンドを実行して表示された情報の「prefix」の場所。
以下の実行例の場合はC:\nodejs\node_modules_global
配下にnode_modules
フォルダ配下にパッケージが配置される。
実行例)
> npm config list
; cli configs
metrics-registry = "https://registry.npmjs.org/"
scope = ""
user-agent = "npm/6.13.4 node/v12.14.1 win32 x64"
; userconfig C:\Users\user\.npmrc
prefix = "C:\\nodejs\\node_modules_global"
; builtin config undefined
; node bin location = C:\Program Files\nodejs\node.exe
4. npm install -gによる資材の配置場所の変更方
npm config set
コマンドでprefixの設定値を更新することで配置場所を変更可能。
例)C:\nodejs02\node_modules_globalに変更する場合> npm config set prefix=C:\nodejs02\node_modules_global
なお、グローバルパッケージの配置場所を変更した場合、Windowsでは新しい配置場所にpathを通さないとnode.jsの実行時にエラーとなった。
5. npm installでインストールされたパッケージの確認方法
npm list
コマンドで確認できる。npm ls --depth=0
とすると、依存関係を除いたパッケージ情報のみ表示できる。
- 「-g」オプションを付けずにインストールしたパッケージ(ローカルパッケージ)の確認
> npm ls
sample@0.0.0 C:\node_sample\sample ← ローカルパッケージの配置場所(npm installコマンドを実行した場所)
+-- cookie-parser@1.4.4
| +-- cookie@0.3.1
| `-- cookie-signature@1.0.6
+-- debug@2.6.9
| `-- ms@2.0.0
+-- express@4.16.4
| +-- accepts@1.3.7
| | +-- mime-types@2.1.26
| | | `-- mime-db@1.43.0
| | `-- negotiator@0.6.2
:
:
- 「-g」オプションを付けてインストールしたパッケージ(グローバルパッケージ)の確認
> npm ls -g
C:\nodejs\node_modules_global ← グローバルパッケージの配置場所
+-- express@4.17.1
| +-- accepts@1.3.7
| | +-- mime-types@2.1.26
| | | `-- mime-db@1.43.0
| | `-- negotiator@0.6.2
| +-- array-flatten@1.1.1
| +-- body-parser@1.19.0
| | +-- bytes@3.1.0
| | +-- content-type@1.0.4 deduped
:
:
6. npm installでインストールされたパッケージの削除方法
npm uninstall
コマンドで削除可能。npm ls --depth=0
でインストールパッケージを表示し、表示されたパッケージの@より前の文字列を指定することで削除ができる。
実行例)ローカルパッケージのexpressを削除する場合
> npm ls --depth=0
sample@0.0.0 C:\node_sample\sample
+-- cookie-parser@1.4.4
+-- debug@2.6.9
+-- express@4.16.4
+-- http-errors@1.6.3
+-- jade@1.11.0
`-- morgan@1.9.1
> npm uninstall express
参考にさせていただいたページ
いまさら聞けない!npmのこれだけは知っておきたい基礎知識
https://www.webprofessional.jp/beginners-guide-node-package-manager/
Qiita npmコマンドの使い方
https://qiita.com/yoh-nak/items/8446bf12094c729d00fe
npmコマンドの直列処理、並列処理の簡単な記述
npm
コマンドを簡単に記述して、直列処理、並列処理を行います。
前提条件
- npmがインストールされていること
- package.jsonがあること
インストール
$ npm install-D npm-run-all
使い方
設定
package.jsonrun-s
の後にコマンドを書くと直列、run-p
の後にコマンドを書くと並列で処理を行ってくれます。
コマンドをhello:*
と書くと、当てはまるすべてのコマンドを処理します。
{"scripts":{"hello:foo":"echo FOO","hello:bar":"echo BAR","hello-s":"run-s hello:foo hello:bar","hello-p":"run-p hello:*"}}
実行
npm
コマンドでhello-s
またはhello-p
を実行します。
$ npm run hello-s
$ npm run hello-p
出力結果
FOO
BAR
参考文献
この記事は以下の情報を参考にして執筆しました。
GETとPOST
node.js学習メモ
関数の引数のrequestには、ブラウザからアクセスがあった時に情報が入ってきて(url,methodなどの情報)、情報を取り出すときは、関数内で
request.メソッド
で情報を取り出せる。
Kabaneroプロジェクトアップデート(2020年2月25日)
昨年プロジェクトが発表されたKabaneroですが順調にいろんなコンポーネントが揃ってきたようです。今日は実際にどんな風に使えるのという観点でまとめてみました。
https://kabanero.io/
GitHub
Kabanero https://github.com/kabanero-io
Kabaneroとは?
昨年 @yamachan360が書かれている基本方針は変わらないです。参考にしてください。
Kabanero 公式ページをざっと翻訳してみた (2019年8月版)
https://qiita.com/yamachan360/items/7b4a53758ecdbe876a5f
-日本IBMの紹介サイト
BLOG IBM から始まった新しいオープン・ソース・プロジェクト Kabanero を使用して、Kubernetes 対応のクラウド・ネイティブ・アプリを迅速に構築する
https://developer.ibm.com/jp/2019/12/05/cloud-native-apps-kubernetes-kabanero/
Kabaneroは上にも書かれていますがある特定のオープンソースソフトというわけではなく、コンテナー化されたクラウド・ネイティブ・アプリケーションの作成から本番環境の Kubernetes 上でのライフサイクルまでのすべてを操作できることを目標にしたプロジェクトです。Kabanero全体のアーキテクチャーは以下のようになります。
この中でKabaneroプロジェクトがメインに開発を進めているのが以下の3つになります。
Appsody https://github.com/appsody
コンテナー内のクラウド・ネイティブ・アプリケーションを作成する際のタスクを単純化するオープン・ソース・プロジェクトです。Appsody を使用すると、開発者は組織の標準と要件に従ったマイクロサービスをものの数分で作成できます。
codewind https://github.com/eclipse/codewind
Codewind は VS Code、Eclipse、Eclipse Che (他にも予定されています) などのよく使われている統合開発環境 (IDE) の拡張機能を提供します。これらの拡張機能を使用すれば、使い慣れたワークフローと IDE でコンテナー内のアプリケーションを作成できます。
Razee https://github.com/razee-io/Razee
Kubernetes 対応のマルチクラスター継続的デリバリー・ツール
それでは実際に上記オープンソースを体験できるワークショップのサイトを見てみましょう。(Appsody, Codewind)
まずは日本語のサイト
Kabanero、Appsody、Codewind を使用して、Kubernetes 上の Spring Boot アプリケーションを作成する
https://www.ibm.com/developerworks/jp/library/kabanero-introduction-to-modern-microservices-development-for-kubernetes/
Appsody の Node.js スタックと Spring スタックを使用して、フロントエンド Web アプリとバックエンド REST アプリを作成する
https://developer.ibm.com/jp/patterns/create-insurance-quote-application-appsody/
appsodyがNode-REDに対応したので使ってみた
https://qiita.com/motuo/items/2c59fd0f4410e8a7d014
英語だとたくさんあるようですが
Appsody
Developing microservice applications with the Appsody CLI
(Appsodyを使ってマイクロサービスのアプリを作ってみよう)
https://kabanero.io/guides/use-appsody-cli/#creating-an-application
Appsodyのコマンドをインストールしてnode-jsの雛形をベースに簡単なアプリケーションを作ってみるもの。所要時間は20分
Developing cloud native microservice applications with the Kabanero Node.js Collection and Appsody CLI.
https://kabanero.io/guides/collection-nodejs/#testing-locally-on-kubernetes
上記とほぼ同じだがKubernetesにDeployするまでの手順がある
Working with Kabanero Collections
https://kabanero.io/guides/working-with-collections/#what-you-will-learn
Appsodyを使ってKabaneroコレクション(アプリケーションの雛形)を作成。
Developing cloud native microservices with the Kabanero Eclipse MicroProfile Collection and Appsody CLI
https://kabanero.io/guides/collection-microprofile/
Appsody cliを使ってMicroProfileのアプリケーションを作成、Kubernetesの環境でDeploy
Developing cloud native microservices with the Kabanero Spring Boot Collection and Appsody CLI
https://kabanero.io/guides/collection-springboot2/
Appsody cliを使ってSpring Bootのアプリケーションを作成、Kubernetesの環境でDeploy
codewind
Getting Started with Codewind and Kabanero
https://kabanero.io/guides/guide-codewind/#objectives
codewindを使ってMicroProfileのアプリケーションを作ってみる。動作環境はローカル
Developing with Kabanero Collections in your Eclipse IDE
https://kabanero.io/guides/microprofile-eclipse-codewind/#what-you-will-learn
codewindを使ってMicroProfileのアプリケーションを作成、実行、テストまで行う。