astamuse Lab

astamuse Labとは、アスタミューゼのエンジニアとデザイナーのブログです。アスタミューゼの事業・サービスを支えている知識と舞台裏の今を発信しています。

Nuxt.jsのライフサイクル覚書

デザイン部でフロントエンドエンジニアをしているkitoです。React.jsとVue.jsの登場で、JS界隈は一時期の混沌とした時代から落ち着いてきましたが、今や両者それぞれのエコシステムが豊かになるフェーズに移行しています。jQueryがデファクトスタンダードになり、盛んにプラグインが開発されていた頃を彷彿とさせます。
Vue.jsのエコシステムのなかでもNuxt.jsは、完成度の高さからVue.jsのサーバーサイドレンダリングのフレームワークとして広く利用されようとしています。

今回は、そんなNuxt.jsを実際のサービスで使うさいに欠かせないライフサイクルの知識について書きたいと思います。といっても、私自身、最近までNuxt.jsのライフサイクルついて十分に理解していたかというと、心もとないところがありました。公式サイトのライフサイクルダイアグラムをみてわかったようなわからないような気になっていたのです。

そのひとつの原因としては、Nxut.jsは、Vue.jsのサーバーサイドレンダリング用のラッパーのようなものなので、それぞれのライフサイクルを頭に入れ「どこからこどまでがサーバーサイドのライフサイクルなのか?」「どこからがクライアントサイドなのか?」を把握しなければならないからでしょう。
今回、曖昧だった疑問を整理し、Vue.jsとNuxt.jsをひとつにつなげたライフサイクルとして図を作成しました。

Nuxt.jsとVue.jsのライフサイクル

早速ですが、下記がライフサイクルの図になります。

f:id:astamuse:20190529112056p:plain

nuxtServerInit

nuxtServerInitは、Nuxt.jsのライフサイクルの最初にくるメソッドで、サーバーサイドから直接データをstoreにセットしたいときなどに使います。ログイン認証でセッションIDをstoreに保存したいときはこのメソッドで行います。まだブラウザのwindowオブジェクトにアクセスできないので、localStorageにデータを直接保存することはできません。

middleware

次に、nuxt.config.jsに記述されているmoduleやmiddleware、pluginが呼び出されます。もし、severMiddlewareとしてexpress.jsを使っている場合もここで呼ばれます。 図では省略していますが、クライアントサイドでnuxt-linkをクリックして遷移したときは、ここまで戻って実行されます。

validate

validateは、動的ルーティングしているcomponentsのパラメータをバリデーションします。

asyncData or fetch

asyncDataでは、外部APIからのデータをサーバーサイドレンダリングしたい場合は、ここでaxiosなどを使って取得します。 fetchは、asyncDataとよく似ています。異なるのはfetchは、値をstoreにセットできますがcomponentsに値をセットできない点です。

render

renderでcomponentsがレンダリングされるわけですが、ここはサーバーサイドとクライアントサイドの境界にあり、beforeCreateやcreatedは両方から利用できるメソッドになっています。

クライアントサイド

これから下はクライアントサイドのみの領域になり、Vue.jsのみのライフサイクルに入っていきます。beforeMonuntはVueインスタンスがマウントされる前に呼ばれ、mountedはマウント直後に呼ばれます。DOMにアクセスしたいならここで。

ライフサイクルの重要性

ライフサイクルへの意識が特に必要になるのは、ログイン機能をつけるときでしょう。ユーザー認証のためにtokenを一定期間保持しなければなりませんが、asynDataではまだブラウザのcookieやlocalStrageにはアクセスできません。最初のリクエスト時、nuxtServerInitでtokenをStoreにセットしておくことが必要になるでしょう。 また、DOMを直接触ろうとするなら、クライアントサイドのライフサイクルに入ってからでないと不可能です。

まとめ

Nuxt.jsなら、モダンなフロンエンド開発に欠かせない設定が隠蔽されているので、容易にサーバーサイドレンダリングされたWebアプリが作成できます。 しかし、ログイン機能のような少し複雑な機能を追加しようとすると、途端にライフサイクルやVuexの状態管理の知識が必要になります。 今後、Nuxt.jsが選択されることが増えていくと思われます。Nuxt.jsへの技術的投資は、さらに重要になっていくのではないでしょうか。

アスタミューゼでは、エンジニア・デザイナーを募集中です。ご興味のある方は遠慮なく採用サイトからご応募ください。お待ちしています。

PM としてアスタミューゼに転職して

はじめまして、PM として入社しました Gyopi です。
アスタミューゼでは複数の Webサービスや社内サービスを持ちながら、
Product Manager を専業でやっている人間がいませんでした。

そんな中入社した一人 PM からみたアスタミューゼと最近やったことのお話です。
(以下、PM は全て Product Manager のことを指します)

(経歴)Webエンジニアの経験を PM に

「Product Manager」とはどいういう役割かプロダクトやフェーズにより異なるかなと思いますが、

Product の魅力によりユーザーに得をさせる。
そのために、メンバーが魅力を作り上げやすい状況を作り出す。

というのがざっくりとした役目だと思っています。

私はキャリアの最初から PM だったわけではなく、
最初は Ruby on Rails で BtoB のサービス開発を行うエンジニアでした。

PM は直接的に開発者の領域を侵してはならないと思いますが、
少なからず開発知識があることは議論において役に立っている実感があります。
エンジニア時代の周囲の人々には今も感謝が絶えません。


また、前職にて新規サービスを立ち上げて拡大していく中で
Growth Hack の試みを行ったり大口顧客に法人営業を行ったり、
ユーザーのことを考え・ユーザーに直に接する機会も得られました。

事業の目線で物事を考える上では新規サービスでの Product Owner(PO)を務めた経験も大きいです。
サービスを拡大させる中で複数の立場を経験できたこと、
その中でもユーザーに直に接した経験は「ユーザーに得をさせる」の発想に至る原体験だと思います。

転職理由)プロダクトとの向き合い方を磨くために

幸いにも新規サービスは成長し、
事業側も開発側もアグレッシブなメンバーに恵まれていましたが、
最終的には5年ほど務めた前職からの転職を決断しました。

法人向けのサービスにやりがいを感じながら、事業として1つだけの領域で試行錯誤をすることに限界を感じていました。
携わっていたサービスが Google などのビッグプレイヤーが競合となるドメインにいたこともあり、より独自性の強いサービスに携わりたかったです。
とはいえ、やりきった達成感が故の転職でもあったように思います。


アスタミューゼへ決めた理由は、採用面接で今の上司から受けた会社やデータの説明が興味深かったから、というのが大きいです。
ロジックで考える条件もありますが、こういう人たちと働くのは面白いだろうと、面接を楽しく感じさせてもらえたことは確実にプラスに作用しました。
最初は別の職種で応募していながら、適性を踏まえて PM を推奨してくださったことも嬉しかったです。


また、以前から Scala で開発していることを知っていたので開発者目線でも以前から知っている会社でした。
転職してから全然プログラミングできてないな、という自戒も込めて敢えて初心を記しておきます。

転職してみて)アスタミューゼで自分に何ができそうか

流れの速い事業・プロダクトに携わっているということでもあるのですが、最初のキャッチアップが何より大変でした。
社内の打ち合わせで「アグリー」とか「ケイパビリティ」とネタではなく口にする人がいて、転職の実感を感じた次第です。
とはいえ総じて周りの方は優しくて、聞いたらなんでも教えてくれるし、ご飯にも誘ってくれるし、お陰様で慣れるのは早かった気がします。


専門用語が飛び交って会話が進むので「これはドメイン知識を得るのが大変だ」ということで少しずつ社内用語集を作りました。
用語集はたまに近くの部署の人に今でも共有することがあり、フットワーク軽く作ってよかったなと思っています。
その後、同じタイミングで入社した池田 (@yukung)が社内 Wiki を整備してくれたので情報共有はかなり前進しています。


アスタミューゼには開発面やインフラ、新規事業コンサルなど様々な経験・スキルを持つメンバーがいて本当に多種多様です。
プロダクトも人数の割に多いですし、何より新規も運用中のものもデータがとにかく多いです。
その中で都合よく一人 PM という立場を利用してフットワーク軽く様々な面に顔を出すことで、意外と気づきづらいチームやプロジェクトの滞りを解消できたらなと思っています。


これまでアスタミューゼのプロダクト設計はどちらかといえば解決策ドリブンのものが多かったようです。
データや社内の専門的知見が多種多様なことを考えると、それにも納得がいきます。
それを新しい Web サービスとしてソリューションだけでなく価値を試行錯誤し、チームみんなで成長していければいいなと考えています。

最近やったこと)サービスについてみんなで議論を

最近やったことの中から、Lean Canvas を書いてみたというエピソードを少し。
Lean Canvas はサービスを「誰のどんな課題を解決するためにどういった機能を届けるか」を固めるものですが、
やったことがないと書いてみることに高いハードルを感じてしまいがちです。

今までにメンバーを少し入れ替えながら何度かトライしてみました。
その中でも一番初めは少し見切り発車気味に進めましたが、
「意外にみんなで埋められる!」という実感を得ることができたのではないかと思っています。


検討を重ねる中で、サービスの何に着目していくかという次の分類を気にしながら取り組みました。
・CPF (Customer Problem Fit)
・PSF (Problem Solution Fit)
・PMF (Product Market Fit)
とはいえ、まだ CPF がひと段落という段階です。
各フェーズなどについてはまた別の記事でお話しできればと思います!


実装する機能や、KPI 達成の施作には日常の中で確実に注目していますが、
「どうしてこういった KPI に落としこまれるのか」は定期的に振り返らないといけないなと改めて感じました。
また、メンバーによってどれくらい詳しく言語化することで判断材料として納得がいくのか、などの特徴も出てきたかと思います。


他にもこの数カ月で開発スタイルがアジャイル・スクラム化されたり、
機能要件を User Story 形式にしはじめたり、
開発チームでモブプロとかはじめたり、
チームの成長がすごいので今から未来が楽しみです!

おわりに

私は Web サービスの PM というところをメインに働いていますが、
社内システムを刷新したり、持つべきデータの企画をおこなったり、事業と技術を繋げていくべき役割が社内にたくさんあります。


決まり文句になりますが、We’re hiring!! ということで
エンジニアやデザイナーだけでなく PM も募集中です!
アスタミューゼに、Product Manager に、興味を持っていただければお気軽に採用情報をご覧になってください!





Blog を一回分飛ばしてしまったので、次回は締め切りにコミットできるようにしたいと思います。

Scalaから始めるRust入門

お久しぶりでございます。Scalaでバックエンドを開発しているaxtstar(@axtstart)です。

みなさまゴールデンウィークはいかがお過ごしだったでしょうか?

我が家はあまり旅行に行くということもなく、近場のドライブや、ちょい大き目の公園などで過ごすことが多かったです。

さて、そのおかげというわけではありませんが、この連休を利用して、 新たにRustとWebAssemblyに入門してきたので今回はそのあたりの話を、書きたいと思います。

f:id:astamuse:20190508020532j:plain

Image by prettysleepy1 from Pixabay

前書き

遅ればせながら、前々から気になっていた、Rust Programming Languageの勉強をGWを利用して初めて見ました。

随分昔ですが、Visual C++でDLLを作ってそれをフロントのVisual Basicで呼び出すのが最強と思っていた時があります。

それと似た世界が、JavascriptとRust(WebAssembly)なのではないかと、最近考えていました。

少々遅い気もしますが、何事もはじめないよりは、はじめた方が面白いし何かの役に立つことがあればいいなと。

普段はScalaで開発する事が多いので、そのScalaとの比較として書いていきたいと思います。

Rust、Scala比較

さて、早速RustとScalaを比較してみます。

◎RustはScalaとは違ってガーベージコレクションを持たない

→自分でメモリ管理をする必要がある*1

とは言え、強力な言語システムのサポートがあるため、昔のように、メモリの解放もれを気にすることはかなり減っているようです。

Scopeを抜けると確保したメモリは解放される*2などなど

→OwnershipメカニズムやScopeメカニズムによって、極力バグをcompile時に発見できる仕組みがある。

 ・stackとheapを常に意識できるような書き方になる

例えば、これはダメ

//Stringはstructであるため、その実体はheap領域に確保される
let s1 = String::from("hello");
//s2はs1をディープコピーするのではなく、参照をコピーしている(シャローコピー)
let s2 = s1;
//memory safetyの観点から、Rustはこの時点でs1をOut of Scopeにする

println!("{}, world!", s1);//<---コンパイルエラー(既にOut of Scopeのため)
println!("{}, world!", s2);//OK

関数に渡しても、Out of Scopeになる

fn bollow(c:String){
}
let s1 = String::from("hello");
bollow(s1);
println!("{}, world!", s1);//<---コンパイルエラー(既にOut of Scopeのため)

これはOK

// 固定文字列はstack上に確保される
let s1 = "hello!";
//これはスタック上の操作
let s2 = s1;

println!("{}, world!", s1);

これもOK

fn bollow(c:&str) {
}
// 固定文字列はstack上に確保される
let s1 = "hello!";
bollow(s1);

println!("{}, world!", s1);

参照渡しにした場合はこの限りではありません。

fn calculate_length(s: &String) -> usize { 
    s.len()
}

let s1 = String::from("hello");

let len = calculate_length(&s1);
// 参照渡しの場合、ここでOwnereshipの変更は無い

println!("{}", s1); //OK

もちろん上記のようなことはScalaではおこらず、コンパイルは通ります*3

◎StatementとExpressionが異なる概念として扱われる

Scalaだとあまり意識しない*4セミコロン(「;」)の有る無しによって、StatementなのかExpressionなのかが決まります。

これはダメ

fn add(x:i32, y:i32) -> i32 {
  // これはStatement
  x + y; // <--i32を戻り値として指定しているので、戻り値=()は、コンパイルエラー
}

これはOK

fn add(x:i32, y:i32) -> i32 {
  // これはExpression
  x + y //ここが戻り値になる
}

◎immutable、mutableはScalaと非常に似ている

immutable

let x = 5;
x = 10; // <-- コンパイルエラー
let x = 6; // <-- これはOK(シャードーイング)※ScalaはScopeで実現

mutable

let mut x = 5;
x = 10; // OK
let x = 6; // <-- OK(シャードーイング)※ScalaはScopeで実現

◎enumが非常に強力

 ・Optionがenumとして実装できる*5

enum Option<T> {
    Some(T),
    None,
}

◎nullが無い

その発明者自身*6が10億ドルの過ちといった「null」がRustには存在しません。

Scalaはnullそのものを消去することはできていません。極力使わないようにプログラミングすることはもちろん可能ではあります。

◎match文はScalaと非常に似ている

Rust

fn fib(n: u32) -> u32 {
    match n {
        0 => 0,
        1 => 1,
        _ => fib(n-2) + fib(n-1),
    }
}

順序と case、「,」以外同じように見える

Scala

def fib(n:Int):Int= {
  n match {
    case 0 => 0
    case 1 => 1
    case _ => fib(n-2) + fib(n-1)
  }
}

◎文字の取り扱いはそこそこ大変

let h = String::from("hello");
let w = String::from(", world!");

let hw = h + &w;
println!("{}", w);// OK
println!("{}", h);// コンパイルエラー、hはStringのadd関数、演算子オーバロード(+)により、Out of Scopeになっている

◎Cargoという、パッケージ管理ツールがある

→Scalaのsbt、gradle、mavenのような存在。

tomlという形式で、設定ファイルを記述します。

ここで紹介した機能以外にも、似た機能や全然似てない機能はたくさんあります。

私も今回はこの*7チュートリアルで学習しました。

WebAssembly

MDN Web Docsによると、WebAssemblyとは、

以前ではできなかったようなウェブ上で動作するクライアントアプリケーションのために、複数の言語で記述されたコードをウェブ上でネイティブに近いスピードで実行する方法を提供します。

というもので、IEを除くだいたいの最新ブラウザなら動き、今回Hello, world!した、Rustとも相性の良いものです。

Hello, world! for WebAssembly

RustでWebAssemblyでHello, world!するには今は、wasm-bindgenを使用するのが一番簡単なようですね。

Cargo.toml

[package]
name = "hello-world"
version = "0.1.0"
authors = ["axt <axt_star@hotmail.com>"]
edition = "2018"

[lib]
path = "src/lib.rs"
crate-type = ["cdylib", "rlib"]

[dependencies]
wasm-bindgen = "0.2"
wee_alloc = { version = "0.4.2", optional = true }

src/lib.rs

use wasm_bindgen::prelude::*;

// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
// allocator.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;

// Rust側からJavascriptのalertを呼べるようにする。
#[wasm_bindgen]
extern {
    fn alert(s: &str);
}

// JavascriptからRustの関数を呼び出せるようにする。
#[wasm_bindgen]
pub fn greet() {
    alert("Hello, hello-world!");
}

上記の2ファイルの状態で、下記を実行すると、しばらくかかりますが、target、pkgというフォルダができて、そこにJavascript*8からRust側を呼び出す、glueスクリプト*9みたいなものを吐き出してくれます。

wasm-pack build

さらに下記で、wwwディレクトリを作成してそこに、webの開発に必要なwebpackなど一式を作成してくれます。

npm init wasm-app www

www配下でnpm installして必要なライブラリを取得します。

cd www && npm install & cd ..

pkgをlinkします。

cd pkg/ && npm link && cd ..

pkgを利用したい側でリンクを呼び出します。

cd www && npm link hello-world && cd ..

ローカルのwebサーバを起動します。

cd www && npm run start ; cd ..

ブラウザでhttp://127.0.0.1:8080/にアクセスすると、alertのダイアログでhello, world!が表示されます。

ここまでここの内容だいたい、そのままです。

RustからのDomアクセスなど

WebのDocumentやElementなどにアクセスしたい場合は、Cargo.tomlにfeatureを追加する必要があります。

[dependencies.web-sys]
version = "0.3.4"
features = [
  'console',
  'CanvasRenderingContext2d',
  'Document',
  'Element',
  'HtmlElement',
  'HtmlBodyElement',
  'HtmlButtonElement',
  'HtmlCanvasElement',
  'EventTarget',
  'Node',
  'Window',
]

bodyの最後にpタグでHello from Rust!の追加

#[wasm_bindgen]
pub fn hello_p() -> Result<(), JsValue> {
    let window = web_sys::window().expect("no global `window` exists");
    let document = window.document().expect("should have a document on window");
    let body = document.body().expect("document should have a body");

    // Manufacture the element we're gonna append
    let val = document.create_element("p")?;
    val.set_inner_html("Hello from Rust!");

    body.append_child(&val)?;

    Ok(())
}

速度を比較してみる

あまりいい例ではないのですが、(スタックオーバフローになるような何も考えてない方法の)フィボナッチ数列による速度比較をして見ました。

Javascript版

function fib(n) {
    return n < 2 ? n : fib(n - 2) + fib(n - 1);
}

WebAssembly(Rust)版

#[wasm_bindgen]
pub fn fib(n: u32) -> u32 {
    match n {
        0 => 0,
        1 => 1,
        _ => fib(n-2) + fib(n-1),
    }
}

3回実施した平均です。

fib(n)

n Javascript(ms) Rust(ms) 割合(R/J)
10 0 0 -
30 17 14 1.2
40 1138 910 1.25
41 1842 1463 1.26
42 2995 2388 1.25
43 4821 3802 1.27
43 7868 6134 1.27

Rust版の方が2割増し程度の性能が出ました。

アルゴリズムが大変悪いので比較的低いnでPageがクラッシュしました。

ただこれだけなのに、速度差を体感できます。

画像を受け渡してみる

Rust側に、canvasのイメージバイナリを渡して透過度をあげてみます。

↓こちらに置いておきます。

github.com

Rust側は比較的簡単に実装できます。

ただバイナリは直列データとして扱う必要があるようです。

線の描画などはもっと別の方法が良いかもです。

Rust側

#[wasm_bindgen]
pub fn to_tranparent(screen: & mut Screen, bytes: & mut [u8]) {
    for i in 0..screen.height {
        for j in 0..screen.width {
            let offset = 4 * (screen.width * i + j);

            bytes[offset] = bytes[offset];
            bytes[offset + 1] = bytes[offset + 1];
            bytes[offset + 2] = bytes[offset + 2];
            bytes[offset + 3] = 25;//透明度を上げる
        }
    }
}

htmlはVue.jsの読み込みと、canvas、inputを置いています。

html

    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="./bootstrap.js"></script>
        <canvas
          id="canvas"
          height="600"
          width="400">
     </canvas>
     <br />
     <input @change="uploadFile" name="image" type="file" />
      <button v-on:click="to_transparent">to_transparent</button>

glue以外の記述部分です。

javascript

var app = new Vue({
    el: '#app',
    data: {
      text: '',
      left_val: '',
      right_val: '',
      number:10,
      message: '',
      canvas: null,
      duration: ''
    },
    methods: {
      to_transparent: function() {
        // canvas取得
        const canvas = document.querySelector('canvas');

        // Rust上のScreen構造体取得
        const screen = new sample.Screen(canvas.width, canvas.height);
        // context取得
        const ctx = canvas.getContext('2d');
        // canvas
        var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
        // Rustの透過処理を呼び出し
        sample.to_transparent(screen, imageData.data);
        // 透過処理後のバイナリをimageに変換
        const image = new ImageData(imageData.data, screen.width, screen.height);
        // canvasに書き戻す
        ctx.putImageData(image, 0, 0);
      },
      uploadFile: function(e){
        // ローカルのファイルアップロード部分
        var canvas = document.querySelector('canvas');
        var ctx = canvas.getContext("2d");
    
        let files = e.target.files;
        var file = files[0];
    
        var image = new Image();
        var reader = new FileReader();
    
        reader.onload = function(evt) {    
          image.onload = function() {
            // canvasにイメージを書き込む
            ctx.drawImage(image, 0, 0);
          }
          image.src = evt.target.result;
        }
        reader.readAsDataURL(file);
      },
    }
})

上記でアップロードした画像の透明化ができます。

まとめ

Rustは非常に面白い特徴を持った言語ですね。WebAssemblyへの対応も進んでおり、比較的楽に開発を進めて行く事ができそうです。

全ての環境では動かない部分もあるので、まずは社内的なプロジェクトのフロントに採用するとか、 限定した環境をうたえるなら結構アリだなと思わせるものでした。

最後になりましたが、アスタミューゼでは現在、エンジニア・デザイナーを絶賛大大大募集中です! 興味のある方はぜひ下記バナーからご応募ください!!

*1:Scalaが全くしなくて良いという意味でも無いですが

*2:C++に見るRAIIに近いが、さらにエレガントな方法

*3:Rustのstructに近い概念はcase classでしょうか?

*4:Scalaではほぼ記述しない

*5:ScalaはOptionとEnumは別のもの

*6:https://ja.wikipedia.org/wiki/%E3%82%A2%E3%83%B3%E3%83%88%E3%83%8B%E3%83%BC%E3%83%BB%E3%83%9B%E3%83%BC%E3%82%A2

*7:日本語版:https://doc.rust-jp.rs/book/second-edition/

*8:Typescriptのglueも!

*9:なんというべきなのか?

Copyright © astamuse company, ltd. all rights reserved.