ユニファ開発者ブログ

ユニファ株式会社プロダクトデベロップメント本部メンバーによるブログです。

サービス開発時のユーザヒアリング

 ユニファCTOの赤沼です。今回はユニファでサービスを開発するときに行なっているユーザーヒアリングについて、少しご紹介します。

 ユニファのサービスは B2B2C の形になっていて、ユニファのサービスを主に保育園に導入していただき、保育士の方にサービスを提供し、さらに保育園に通っている園児の保護者の方にサービスを提供しています。ですのでユニファの営業メンバーはサービスを導入していただくために日々保育園を訪問し、サポートのメンバーはすでにサービスを導入していただいた園からの問い合わせの対応や、各種ご案内を行なうなど、日々ユーザの方とコミュニケーションをとっています。開発のメンバーは日常的には直接ユーザとコミュニケーションをとることはありません。こういった形は多くの会社と同様かと思います。

 ですが、ユニファでは新しいサービスや機能を開発する際には、開発メンバーもユーザの方に積極的にヒアリングを行わせていただいています。主なユーザは保護者の方や保育士の方でして、保護者ユーザの方は主に社内メンバーの友人・知人でお子さんが保育園に通われている方にご協力いただいています。保育士や園長先生の方はユニファの既存サービスをすでに導入いただいている園の方や、ユニファのサービスに興味を持っていただいている園の方にご協力いただいています。

 また、園の訪問の仕方についても、代表の土岐や営業メンバー主導で、開発メンバーが帯同する形もありますが、開発メンバーが自ら日程調整して、資料等も自分で用意して園を訪問することもあります。ヒアリング内容としては、サービス検討段階であれば主に現在の園内業務がどのように行われているか、どんなところを課題と思われているかといったことを聞かせていただき、プロダクト開発段階であればプロトタイプ段階のサービスを使っていただいて感想を聞かせていただいたりしています。

 ユニファに限らずですが、自分を始め開発側の人間はあまり外部の方とのコミュニケーションには慣れておらず、どちらかというと不得意なメンバーも多いので、一度の訪問でも、特に自分主導で訪問する場合は普段の開発業務とは違った消耗の仕方をしますが、直接のヒアリングは開発メンバーにとっても必要なことだと思っています。

 メリットとしては、やはり直接ユーザの声を聞けるというのは、人伝手で聞くのと違ってユーザの温度感を直接感じることができ、要望等についても本当に必要としている感じや、逆にあったらいいな程度のものなのかというのも感じられます。それに実際の業務を見させてもらうことで、どのような現場でどのように業務が行われているのか、保育士の方がどんな方々なのかということもわかります。特に保育士の方の、保育にかける情熱については驚かされます。また、プロトタイプ等を使っていただいて良い反応をいただけると、作っている側としては単純に嬉しいもので、それ以降の開発についてのモチベーションにもなります。

 こういったことは、サービス要件を考えるメンバーはもちろんですが、他のメンバーにとっても開発中の細かい判断に効いてきます。保育業界向けの B2B2C サービスを提供するユニファとしては、一般的な 2C のサービスとも全く対象が異なるので、こういったメリットはさらに大きいと思っています。

 ところでこういったヒアリングでは、もちろん当日のヒアリング自体も大事なのですが、さらに大事なのは事前準備で、そのヒアリングや検証で何を確認したいかを具体化しておくことが必要です。事前準備なくいきなりヒアリングをしても、結果をその後のステップに活かそうとした時に、情報が足りていないということになります。そのヒアリングで何を確認できればサービスの検討を先に進めることができるのかを具体化して、そのために必要な資料等を準備した上で、当日に臨むということが重要です。

 そして得られた回答を検討する段階で重要なのは、取捨選択です。いただいた意見や要望は貴重なユーザの声で、全て対応したくなってしまうものですが、あくまでユーザの一部の声であり、それが大多数のユーザの声と一致するものばかりではありません。全てのユーザの要望を満たすことができればもちろんそれが良いですが、そのための個別対応をしていくと、システムの構成もサポートの要件も複雑化してしまうので、現実的ではありません。サービスの方向性等も踏まえ、取捨選択の基準を内部で決めておきたいところです。

 また、実装すると決めたものでも、初回リリースから必要なものなのか、次以降のリリースでも問題ないのか、ユーザはこう言っているが、実は本当に満たしたい要件はこういうことで、それならば違う解決方法が良いということはないのか、と言った視点でも考える必要があります。

 さらには開発プロセスにおける各ポジションのメンバーの関わり方としても、例えば開発メンバー主導でヒアリングを進めて要件を詰めて行った場合、営業やサポートメンバーの声をどのように取り入れ、どう巻き込んでいくか、プロジェクトのマネジメントはどのポジションのメンバーが行うのか、などの課題もあります。

 これらの内容についてはユニファでもまだこれだというスタンダードが決まっているわけではなく、日々試行錯誤をしつつ進めています。プロジェクトの進め方についてはサービスの対象の業界や会社規模、メンバー構成等によって様々で正解がない領域だと思いますが、より良いサービスを提供できるよう、日々改善を図っていきたいと思っています。

エラー「A copy of xxx has been removed from the module tree but is still active!」と「ObjectContainer」

こんにちは、システム開発部のちょうです。今回はあるエラーから1つ小さいなライブラリを作った話について共有したいと思います。

先月の開発でまれに「A copy of xxx has been removed from the module tree but is still active!」というエラーに遭遇しまして、一回あったらRailsアプリを再起動しないといけない状態になります。回数が少ないですが、開発の邪魔になるので、少し調べました。

ここまでわかったのは、

  1. 解決策1つめ、Fooを::Fooに変更
  2. 本番環境は出ない(自分の経験でも開発環境以外見たことはない)
  3. エラーメッセージによると、モジュールは削除されましたがまだ使われてる(正直ピンとこない)

ひとまず、解決策はわかったので、使ったら元々エラーになった箇所も確実に直りました。でもたまには別のところで同じエラーで怒られました。すべてのクラス名をフルネームで書き直すのは煩雑な作業になるから、やはり根本的な原因を探さないといけないと思いました。

エラーメッセージの一部をキーワードとしてプロジェクトとライブラリ内で検索してみたら、ActiveSupport::Dependenciesのソースコードでこのエラーメッセージが見つかりました。

module ActiveSupport
  module Dependencies
    def load_missing_constant(from_mod, const_name)
      # ...

      unless qualified_const_defined?(from_mod.name) && Inflector.constantize(from_mod.name).equal?(from_mod)
        raise ArgumentError, "A copy of #{from_mod} has been removed from the module tree but is still active!"
      end

      # ...
    end
  end
end

breakpointを入れて開発環境で何回debugしてみましたが、残念ながら、一回も再現できませんでした…

続きを読む

Railsのapp直下でよく使われるディレクトリとその用途を調べてみる

こんにちは、Webエンジニアの本間です。 最近、Railsアプリケーションのディレクトリ構成に関して考える機会があったので、そのことについて書いてみようと思います。

Railsに限らず「ディレクトリ構成をどうするか」という点は、設計初期の悩ましい問題の1つだと思います。 Railsでは標準のディレクトリ構成が決まっているため悩みは少ないのですが、多くの場合、標準外のディレクトリもある程度は追加していくと思います。 特に「app」以下のディレクトリ構成は、アプリケーション全体の設計に関わる部分であり、各メンバーの考えもあるため、なかなか決まらないケースもあると思います。

この問題に対する1つの参考情報として、著名なOSSのRailsアプリケーションが採用しているapp直下のディレクトリを調べてみようと思いました。 著名なOSSで採用されているのであればそれなりの理由があるはずだと思いますし、世の中の標準にも近いはずなので、あとから参加したメンバーも違和感なく開発できると考えたためです。

続きを読む

AWS Lambdaのログを手元へ

久しぶりにブログ書きますUniFaのインフラ見てますすずきです。

あっ、見てますすずきです。って読みづらいですね!!

どうでもいいことは置いておいてさっそく本題へ AWS Lambda ですがサーバレスやマイクロサービスなどで人気ですが。
そのLambdaのログは勝手に保存されていきますよね。(CloudWatch Logsにロググループ作成済みでLambdaに付与したロールに権限があれば…)

CloudWatch Logsのコンソールであれば検索とかできますが、ローカルでgrepとかして検索したいとか言う場合に、 どうやってローカルに持ってくるかの方法を今日は書いてみようと思います。

続きを読む

システムも世の理からは逃れられないという話。

みなさん、こんにちは。 エンジニアの田渕です。

梅雨真っ只中の今週、皆様いかがお過ごしでしょうか?
暑かったり寒かったり、じめじめしてたり……気候が安定しないおかげで、私の周囲では体調を崩す人が続出しています。

さて、今回は、エンジニアブログなのにエンジニアではない方にも読んで頂けそうな記事を書いてみようと思います。
というのも、先日、弊社の営業さんから「読んでおくと良いシステム系の本教えてください」とリクエスト頂いたのですが、なかなかご紹介できる本が無くてですね……。 ということで、自分で記事を一つ、かいてみようかなと。

エンジニアが不思議に思うこと。

長いこと、エンジニアをやっていると、エンジニアとそうではない人の間に、結構大きな感覚の違いがあることに気づきます。 その筆頭にあげられるのが、いわゆる、システムのパフォーマンス、限界についての話。 ちょうど先日も、某大手通販の会社さんがセールを始めた途端、サイトに人が殺到して、繋がりにくいという状況が起こりました。 エンジニアなら割とみんな、「あー、あるある。まー仕方ないよねー。」ってなるんですが、エンジニアでない人と話をしていると「対策しておけよ!」と結構、お怒りだったり。

昔から、うっすら私が思っていることは、
「もしかして、エンジニア以外の人には、システムの世界は簡単に限界なくなんでもできる世界に見えてるのかしら?」 ということです。 まるで、世の中の物理的な法則に縛られていない、別次元の世界の話のような。 いやいやそうじゃないんだよ、システムの世界も、現実世界と同じようなルールに縛られてるんだよ、というのが今日のお話です。

夏休みのアミューズメントパークは長蛇の列

もうすぐ夏休みシーズンですね。 夏休みになると、人気のあるアミューズメントパークの入場ゲートは長蛇の列になります。 行ったはいいけど、入場規制や、あまりの列の長さに涙を飲んだ、という過去をお持ちの方もいらっしゃるはず。 あれ、どうして起こるのでしょう? 答えは簡単で、入場しようと訪れている人の人数に対して、開いている窓口が少ないからです。 窓口が10個開いているところに11組のお客様が同時に来たら、1組は並んで順番を待つことになります。

じゃあ100組同時に来たら?200組同時に来たら? 最後に来た人は、自分の前に広がる列を眺めて、遠い目をするに違いありません。 自分が入場できるまでにとても時間がかかることが、目に見えてわかるからです。

実は、Webシステム/サイトの処理が遅くなる/繋がりにくくなる、という事象の背後で起こっていることの多くは、これと同じようなことです。 Webシステムにはあらかじめ、同時に開いていい窓口の数が設定されています。 この窓口の数よりも少ない人が一斉にやってくる分には何の問題もないのですが、この窓口の数よりもたくさんの人が一斉にやってくると、アミューズメントパークの窓口と同じく、処理の順番待ちが発生します。 ここで、現実世界なら「あ〜前に人が並んでるからね〜。」と自分の目で見て状況を把握出来ますが、Webサイトに訪れたお客様には自分の前に並んでいる他のお客様の姿など見えていません。結果、いつまでも処理がされないまま、ただ待たされ「遅い」「繋がらない」と感じることになります。

お客様に早く入場してほしい!

「いやいやいや。待たせるなんてもってのほかだよ!早く入場してもらってよ!顧客満足度ダダ下がりじゃん!」
……おっしゃる通りです。 この場合、施設側がとれる対処法はいくつかあって、

  1. 入場処理の効率化、簡略化をする……1組あたりにかかる入場処理の時間を短くして、入場できるお客様の数を増やす方法です。

  2. 窓口の数を増やす……入場処理のやり方は変えず、単純に窓口の数を増やすことで、入場できるお客様の数を増やす方法です。

システムの性能改善、パフォーマンスチューニングと呼ばれるものも、概ね同じようなことをします。 「なんだ、窓口の数増やせばいいなら、常に多目にしとけばいいじゃん。」と思うかもしれませんが、窓口は多ければ多いほどお金がかかる。それは、現実世界と一緒です。

アミューズメントパークが繁忙期の人の山に即時に対応できるだけの窓口を作らないのと同じように、 コンビニのレジがお昼時に合わせてレジの数を決めないように、 システムも基本は、「繁忙期に合わせて」ではなく、「平常時の来客に対応するに十分」なだけの窓口しか開けません。 そうじゃないと必要以上にお金がかかるからです。

状況に応じて窓口の数を増やす方法はないのか?

年賀状の配達のために某所が一時的にアルバイトを増やすように、じゃあその時だけ、一瞬窓口増やす方法ないの? と言われると、実はあります。近年ではそのあたりの技術が飛躍的に発達し、あらかじめ設定しておけば機械が自動的に窓口を増やすことも出来るようになったので、システム利用者が長蛇の列に並ぶ機会は一昔前に比べると随分減っています。ただ、「無限に増やしていいよ」という設定にはできないので、「ここまでなら窓口増やしていいよ」という感じの設定をあらかじめしておくことになります。その数を超えてしまった=当初の予想を超えてしまえば、やっぱり長蛇の列はできます。

そんな訳で、未だにインターネットの世界でも、長蛇の列に遭遇することになります。

さて、今回は、システムの利用者が並ぶ長蛇の列について解説してみました。 形がないからイメージしづらいインターネットの世界ですが、実はあまり、現実の世界と変わりません。 これからは、「読み込み中」のままでなかなかお返事が帰って来ない画面に遭遇したら「ああ、私今、窓口の前で待たされてるのね。」と思ってください。

ではでは。

イメージで覚えるReact + Redux

こんにちは、tanaka です。

Rails5.1で Webpacker が導入されて React や Vue.js などのフロントエンド技術がさらに身近になりました。ヽ( ´¬`)ノ

ということで、早速 Rails5.1 + React + Redux でちょっとしたアプリを作ってみるぜ !! とコーディングを始めたところ、

・・・・

ちょっと昔に ReactNative を勉強したときに使ったはずの Redux をガッツリ忘れているではありませんか …。 (ノ゚ο゚)ノ

そんな忘れん坊な私のためにReact + Reduxを復習し、今度こそ忘れないぞと誓うとともに記事としてまとめました。

React + Reduxの参考記事

React + Reduxについては詳しい記事がたくさんあります。 私も数回読みました。どれも分かりやすくて良い記事だと思います。

が、それでもまだチョット・・という自分のためにTodoアプリを例に出しつつ図を描きながら説明を試みます。 Reduxのスタンダードな解説はこれらの記事に譲ります。

React + Redux のキホン

覚えることは3つだけです。

  • アプリケーションのすべての状態は ストア で一元管理する。
  • ビューは state と action creator を受け取り、stateに従って描画するだけ
  • 描画を変えたいときはビューが action を発動して state を更新する。(その結果、描画が更新される)

f:id:sanshonoki:20170707113102p:plain

個人的に action は発射ボタンとしてイメージすると理解しやすくしっくりきました。 ビューは発射ボタンを受け取って<input /> などの要素のイベントに紐付けておき、マウスクリック等のユーザーイベントが発生したらそのスイッチが押されるイメージです。

Reducerは状態の管理人というイメージを持ちました。複数の管理人がそれぞれアプリケーションの部分状態を管理しているイメージです。

Reduxは下の図のような 一方向のフローとしての図をよく目にしますが↑の図のほうが実体に則している気がして私にはしっくりきました。

https://github.com/facebook/flux/raw/master/examples/flux-concepts/flux-simple-f8-diagram-with-client-action-1300w.png https://github.com/facebook/flux/tree/master/examples/flux-concepts

ビューは2種類

ビューは コンテナ と コンポーネント の2種類があります。

  • コンテナ(器)
    • ストアからコンポーネントで利用する state を受け取る
    • コンポーネントで使用する action creator のリストを受け取る
  • コンポーネント(部品)
    • コンテナから state や action のリストを渡され描画する
    • マウスクリック等のユーザーイベントがあったら対応する action を発動する (dispatch)

f:id:sanshonoki:20170707141542p:plain

つまり、コンテナが React外部とのインターフェースとなります。

この説明ではトップがコンテナになっていますがトップにコンポーネントがあり、その中に複数のコンテナがあるというパターンもあります。その場合は、各コンテナがそれぞれ自分がもつコンポーネントの描画に必要な状態やアクションをストアから取得します。

全体像をコードつきで

f:id:sanshonoki:20170707114002p:plain

connectは引数なしとありで挙動が変わるので実はややこしいのですがその辺は次の記事に詳しく書かれています。

qiita.com

実際のコード

Actions

アクションはアクション種別とアクションに付随するパラメータを store に渡すだけです。

app/javascripts/packs/actions/index.js

let nextTodoId = 1

export const addTodo = text => {
  return {
    type: 'ADD_TODO',
    id: nextTodoId++,
    text
  }
}

export const toggleTodo = id => {
  return {
    type: 'TOGGLE_TODO',
    id
  }
}

export const enableUI = () => {
  return {
    type: 'UI_ENABLE'
  }
}

export const disableUI = () => {
  return {
    type: 'UI_DISABLE'
  }
}

Reducers

Reducer は状態を新しく生成するだけのpureな関数です。引数で与える state が初期状態となります。

ポイントは

  • 元の state を編集せず 新しい state を返すこと

です。元のstateの値を編集してもビューは更新されないので要注意です。

app/javascripts/packs/reducers/todos.js

const todos = (state = [{id:0, text:"first task (click me)", completed:false}], action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return [
        ...state,
        {
          id: action.id,
          text: action.text,
          completed: false
        }
      ]
    case 'TOGGLE_TODO':
      return state.map(todo =>
        (todo.id === action.id) 
          ? Object.assign({}, todo, {completed: !todo.completed})
          : todo
      )
    default:
      return state
  }
}

export default todos

app/javascripts/packs/reducers/settings.js

const settings = (state = { enabled: true }, action) => {
  switch (action.type) {
    case 'UI_ENABLE':
      return { enabled: true }
    case 'UI_DISABLE':
      return { enabled: false }
    default:
      return state
  }
}

export default settings

app/javascripts/packs/reducers/index.js

import { combineReducers } from 'redux'
import todos from './todos'
import settings from './settings'

const todoApp = combineReducers({
  todos,
  settings
})

export default todoApp

combineReducersで todos と settings の2つの partial state をグローバルな state として束ねます。このグローバルな state を store に渡します。

Containers

app/javascripts/packs/containers/App.js

import React, { Component } from 'react'
import { connect } from 'react-redux'

import { toggleTodo, addTodo, enableUI, disableUI } from '../actions'
import TodoList from '../components/TodoList'

const mapStateToProps = state => {
  return {
    todos: state.todos,
    enabled: state.settings.enabled
  }
}

const mapDispatchToProps = dispatch => {
  return {
    onTodoClick: id => {
      dispatch(toggleTodo(id))
    },
    addTodoClick: text => {
      dispatch(addTodo(text))
    },
    toggleUI: checked => {
      checked ? dispatch(enableUI()) : dispatch(disableUI())
    }
  }
}

const App = connect(mapStateToProps, mapDispatchToProps)(TodoList)

export default App

mapStateToProps関数でグローバルなstateから必要な partial state を取り出し、propsオブジェクトにマッピングしてコンポーネント(TodoList)に渡します。 同様に mapDispatchToProps関数で action creators のうち必要な action を props にマッピングしてコンポーネント に渡します。

コンポーネントからは state.settings.enabledthis.props.enabledとしてアクセスできます。 onTodoClick関数は this.props.onTodoClick(id)として呼び出せます。

Components

app/javascripts/packs/components/TodoList.js

import React, { Component } from 'react'
import { render } from 'react-dom'

const Todo = ({onClick, completed, text }) => (
  <li
    onClick={onClick}
    style={{
      textDecoration: completed ? 'line-through': 'none'
    }}
  >
    {text}
  </li>
)

const AddTodo = ({ onClick, enabled }) => {
  let input

  return (
    <div>
      <form
        onSubmit={e => {
          e.preventDefault()
          if (!input.value.trim()) {
            return
          }
          onClick(input.value)
          input.value = ''
        }}
      >
        <input
          ref={node => {
            input = node
          }}
        />
        <button type="submit" disabled={!enabled}>
          Add Todo
        </button>
      </form>
    </div>
  )
}

const TodoList = ({ todos, enabled, onTodoClick, addTodoClick, toggleUI }) => (
  <div>
    <AddTodo onClick={addTodoClick} enabled={enabled} />
    <ul>
      {todos.map(todo => (
        <Todo key={todo.id} {...todo} onClick={() => { if (enabled) { onTodoClick(todo.id) } }} />
      ))}
    </ul>
    <input type="checkbox" checked={enabled} onChange={(e) => toggleUI(e.target.checked)} />UI Enabled
  </div>
)

export default TodoList

コンポーネントは props、ここでは { todos, enabled, onTodoClick, addTodoClick, toggleUI }のオブジェクトをコンテナから受け取って描画とイベントバインディングを行います。 ユーザーがクリックしたらイベントに紐付けられた action が発動します。

今回は1つのファイルにTodoやAddTodoなどのサブコンポーネントを含めていますが通常は別ファイルで定義します。

プロダクトレベルのアプリケーションに向けて

プロダクトレベルのアプリケーションを実装するときはさらに

  • 非同期処理(API叩くときetc..)
  • ルーティングによるページ遷移
  • Global state と Local state の共存

を考えていく必要があります。 私も今まさに勉強中なので詳しくは書けませんが勉強した範囲でポイントとなりそうなことを書いておきます。

非同期処理

非同期処理のポイントは

  • データをAPIから取得するアクション
  • APIから取得したデータを state に反映するアクション

を分けて実装することです。

具体的なコードは以下のような感じになります。(react-thunkを使います)

export const addTodo = (id, title) => {
  return {
    type: 'ADD_TODO',
    id,
    title
  }
}

export const createTodoAsync = title => {
  return (dispatch, getState) => {
    const _todo = { title: title, completed: false }

    fetch('/api/todos', {
         method: 'POST',
         headers: {
           'Accept': 'application/json',
           'Content-Type': 'application/json'
         },
         body: JSON.stringify(_todo) })
    .then(response => {
      return response.json()
    })
    .then(data => {
      dispatch(addTodo(data.id, data.title));
    })
  }
}

ルーティング

複数のページをもち、ページごとにビューを切り替えたいときはルーティングを行います。 ルーティングは react-router-dom というパッケージを利用します。

基本的な使い方は medium.com が参考になります。

import { BrowserRouter as Router, Route } from 'react-router-dom'

document.addEventListener('DOMContentLoaded', () => {
  ReactDOM.render(
    <Provider store={store}>
      <Router>
        <Route exact path="/" component={MainPage} />
        <Route path="/contact" component={ContactPage} />
      </Router>
    </Provider>,
    document.body.appendChild(document.createElement('div')),
  )
})

のようにURLのパスに応じて表示するコンポーネントが選択されるようになります。

また、action(ビュー外)からページ遷移する方法は少しトリッキーになっていて以下が参考になりました。

Local stateとの共存

Redux ではアプリケーションの状態はすべてストアで一元管理することになっています。しかし、そうなると特定のコンポーネント内でしか使わない state もストアで管理することになりストアが肥大化、複雑化しすぎるという問題が出てきます。

そこで Reduxで管理するグローバルな state と各ビュー内のローカルな state を分けて管理するというやり方も考えていく必要があるでしょう。

made.livesense.co.jp

Rails + React + Redux 導入方法

最後に Rails で React を使う方法を紹介します。 RailsにReactを組み込むこと自体はかなり簡単でものの数分で準備できちゃいます。

注意点としては

  • yarnをインストールしておくこと (0.20.1+)
  • Node.jsのバージョンが古いとwebpackerがインストールできないのでバージョンを上げておくこと (6.4.0+)

ぐらいです。

手順は

  1. React込みのアプリを作成する

    $ rails new --webpack=react myapp

  2. 適当なページ作る

    $ rails g controller page hello

  3. layoutファイル修正 (app/views/layouts/application.html.erb)

    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>

  4. サンプルのjsxを組み込む (app/javascripts/pack/application.js)

    require('./hello_react.jsx') // 追加する

  5. ビルド

    $ bin/webpack

  6. サーバーを起動してブラウザで表示 (/page/hello)

    • Hello Reactが表示されるはず

2-3分でHello Worldまで行き着けます

これだけだとreduxは使えないので 次に reduxで必要なパッケージをインストールします。

$ bin/yarn add redux
$ bin/yarn add react-redux

環境構築は以下のページを参考にしました。

あとは app/javascripts/packs/ 以下に作りたいアプリケーションを実装していくだけです。

Happy coding ! ┗|l`・ω・´l|┛