Medley Developer Blog

株式会社メドレーのエンジニア・デザイナーによるブログです

思考とデザインスキル 〜メドレーTechLunch〜

はじめまして!最近みるみる太りだしてはいるものの、まだ機は熟していないとダイエットの時期をぐっと堪えている開発本部イケメン担当のデザイナー・小山です。

メドレーではTechLunchという社内勉強会を実施しているのですが、前田に引き続き私も発表する機会をいただきましたので、その内容を紹介させていただきます。テーマは「思考とデザインスキル」です。発表資料は記事の最後をご覧ください。 

 

テーマに入るまえに… 

みなさん『黄金比』ってご存知ですか? 

f:id:medley_inc:20170913121842p:plain

黄金比は美しいとされる物の形に共通してみられる比率で、古くから絵画や彫刻、建築などに使われています。デザイナーであれば、一度以上は使ったことあるのではないでしょうか?私もデザイナーなので何度も使っています。

ただ使ってはいるものの、なぜ美しくなるのか上手く説明ができません。当たり前のように世の中に広まっていて『困ったときの黄金比!』といった安易な思考で使っています。三十路を軽く超えたのでそれではいけないと思い、すこし調べてみました。

そもそも黄金比はエウクレイデスというユーグリッド幾何学を体系化した数学者が見つけた比率なのですが、そのとき彼は『黄金比=美しい』とは明言していないそうです。自然物や人工物の形には一定の比率で成り立っていると考え、その比率に黄金比と名付けだけとのこと。

その後、その使いやすさから至るところで活用され、その比率に親しみが芽生え巷で受け入れられるようになりました。

認知心理学では、それを『単純接触効果』と呼びます。たくさん触れるうちに親しみが沸く機能が人にはもともと備わっていて、黄金比をつかったものが美しく見えるのもその影響ではないか?と言われています。この説を聞いた時、基礎的なのに原理が曖昧なデザイナーのスキルの輪郭がすこしだけハッキリしてきました。

今回のTechLunchでは、曖昧だったデザインのスキルを人の思考や心理現象という視点から捉え直してみると、新しい発見があるのかも?と考え、『思考とデザインスキル』のテーマを選びました。 

『早い思考』と『遅い思考』

まず最初に人の思考がどういう構造になっているか整理したいと思います。

*思考の捉え方には幅があるため、今回はWikipediaで【思考】の狭義にあたる『情報処理』の内容になります。

 

ここで3つの質問を左から順に答えてください。

f:id:medley_inc:20170913122003p:plain

大抵の人であれば2問目までは瞬時に答えが出たと思います。3問目はどうでしょうか?

3問目は他よりも時間がかかったかと思います。情報を処理するときの思考には1問目と2問目のように瞬時に答えれるのは『早い思考』、3問目のように少し時間は必要な『遅い思考』の2つがあります。

行動経済学では、この早い思考を『システム1』、遅い思考を『システム2』と呼んでいます。 

システム1は、自動的に直感で動く

では次にこちらをご覧ください。

f:id:medley_inc:20170913122043p:plain

どちらの直線が長いでしょうか?答えはどちらも同じです。この問題をご存知の方でも見た瞬間はAが長く見えるのではないでしょうか?

ではこちらではどうでしょうか?

f:id:medley_inc:20170913122101p:plain

こちらの文字をみたときに、海水浴していて溺れている、もしくはそれに近いシーンを思い浮かべてないでしょうか? この二語のあいだには『りんご あまい』というような直接の相関はありません。それにもかかわらず出来れば避けたくなるようなことでも無意識に関連づけられ、シーンが思い浮かんだはずです。

システム1には、さきほどの直線の長さのように間違っていたとしても『見たまま』を認識する機能と2つの文字から『関連づけ』をおこないストーリーを組み立てる機能があります。そのどちらもが自分の意識とは関係なく自動で、しかも強力に働いています。

システム2は、手動で論理的に動く

 最初の二桁の掛け算を思い出してください。日本で算数を学んだ人であれば、二桁の掛け算のとき『考える』段階に移ると思います。これがシステム2のスイッチです。システム1は常時スイッチが入っていてほぼ自動で答えを出しますが、システム2は意識的に『考える』というステップを踏まないと動きません。システム1は自動ですが、システム2は手動です。

手動のため手間かかりますが、システム1にはない用心深さと慎重さがあります。こちらの問題をご覧ください。

バットとボールで110円、バットはボールより100円高い、ボールの値段は?

即答で答えた人は10円と答える方が多いようです。こういう問題にはシステム2がうってつけで、論理的かつ正確に答えを出そうとします。答えは5円です。なかには頭の回転が早く即答できる人がいらっしゃることだと思います。そんな方にはこちらの問題を答えていだだきましょう。

3日前から食べた夕飯の献立を口に出して発表しながら、『26x673』『245x287』『346x4546』の3問を90秒以内に解答してください。

いかがでしょうか?システム2は手動でうごき論理的で正確に答え出そうとしますが、複雑すぎる演算やマルチタスクにめっぽう弱くスタミナもありません。あきらめて電卓を叩いた方は、システム2がギブアップしたということかもしれません。

システム1と2の特徴は以下。どちらも良いところとそうでないところがあります。 

f:id:medley_inc:20170913122233p:plain

デザインを再構築

これらシステム1&2の特性を踏まえた上で簡単なニュース記事のタイポグラフィを整理してみました。 

発表資料の58ページからご覧ください。

speakerdeck.com

こと細かく情報をシステム1&2のフィルターを通すことで、タイトルやリード、本文の行間、文字サイズ、それらがシステム2が情報を理解しやすくするための1つ機能として捉えることができます。当たり前に使っていたスキルや機能を別の視点で捉えることで違った深い意味を見出せるようになり、大きな発見につながりました。

今回はタイポグラフィだったのでシステム2寄りのものでしたが、ビジュアルアイデンティティが強く抽象的なデザインは、意図的にシステム2を封じ込めシステム1の直感性を利用して内容を理解してもらうこともできます。

システム1&2の2つの特性を踏まえて使いこなすことで、スキルの深い理解はもちろんですが、感覚とは違うデザインの伝え方の新しいヒントにもなりそうです。

さいごに

YouTubeInstagram、LINEはどちらかというと直感性を促すシステム1が活躍するアプリです。それだけでなく身の回りにあるサービス全体がその傾向という印象をうけます。システム1は普段からスイッチが入っているため、比較的に簡易なステップで働きかけることができます。

一方でシステム2は複雑な演算には耐えれず、しっかりとケアしながらでないと十分な運用ができません。ただケアをしっかりすると恩恵も大きいといえます。

金融、教育、雇用、そして医療と、20年前とは比較にならないほどインターネットは人生の節目に深く関わるようになったからです。人生に関わる選択をネットで行うとき、直感だけでなくシステム2を働かせて熟考し納得のいく決断を行うことが、よりユーザーの利益につながると私は考えています。

メドレーで提供しているサービスはユーザーの人生に少なからず関わる性質をもっています。すぐに結論づけしてもらうより、ユーザーにとって正しいと思える判断ができるように、システム2をうまく働かせることができる環境をデザイナーとしてつくっていきたいと思います。

おまけ

 今回のテックランチで参考にさせていただいた書籍は以下のものになります。

 ファスト&スロー(上・下)』ダニエル・カーネマン

予想通りに不合理』ダン・アリエリ

錯覚の科学』クリストファー・チャブルス&ダニエル・シモンズ

UIデザインの心理学』ジェフ・ジョンソン

今回のお話は、本当に本当に表面の部分になります。流し読みでも参考になるので手に取って見てください。またこういうことに興味がわいたデザイナーさん・エンジニアさん、是非是非メドレーに遊びに来てください!(絶賛募集中です!)

お知らせ

メドレーでは、ジョブメドレーだけでなく、医師たちがつくるオンライン医療事典「MEDLEY」やオンライン診療アプリ「CLINICS」、口コミで探せる介護施設の検索サイト「介護のほんね」などのプロダクトも提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。

www.medley.jp

今後ともメドレーを、よろしくお願いいたします!

クライアント認証とPath Based Routingが必要なサーバをAWSで構築(後編:App層)

今回の内容について

メドレー開発本部の田中です。 先日、Proxy層をElastic Beanstalk上のNginxで、App層をEC2インスタンスで構築する機会がありました。ここだけ見るととても普通に見えますが、制約があることで苦労した点もあり(前編参照)、制約を乗り越えるための工夫も含めてお話できる限り共有させていただきます。 前編ではProxy層の構成として、主にNginxを使用したPath Based Routing周りについてのお話でした。後編ではApp層で使用したEC2、 Systems Managerパラメータストアあたりについて共有いたします。

App層の構成

App層の方針や構築の流れ等をまとめると以下の通りです。

  • ゴールデンイメージとしてOS設定やサーバアプリケーションをインストールしたimage(AMI)を作成しておく
  • 上記のAMIを元に、クライアント毎にEC2インスタンスを作成する
  • EC2インスタンス起動時に、クライアントに応じたTagや環境変数をもとにサーバアプリケーションのセットアップを行う
  • 自身の内部IPとTagに設定したクライアント識別IDを元にRoute53のPrivateDNSに登録する

それでは、それぞれの詳細について説明していきたいと思います。

AMI作成

Packerを使用して各インスタンス共通となるAMIを作成します。provisionersで指定した構築用スクリプトでOS設定や必要ライブラリ、またメインとなるサーバアプリケーションをインストールします。また、cloud-initを使用して初回起動時に動かすスクリプト類もコピーしておきます。

なお、cloud-initから実行するスクリプトはGitやS3などから動的に取得する方法もありますが、さほどスクリプトの内容に変更は発生しない点と、内容的に変更ある場合はimage再作成がどちらにしても必要になりそうだったので割り切ってimage内に含めることにしています。

作成したpacker.jsonprovisioners部分を抜粋するとこのような感じになります(説明コメント部分は実際には記載していません)

  "provisioners": [
    -- type: shellとして、構築用スクリプト指定。ビルド時に実行される
    {
      "type": "shell",
      "scripts": [
        "scripts/provision.sh"
      ]
    },
    
    -- type: fileでインスタンス起動時に実行させるスクリプト群をコピー
    -- これらのスクリプトはcloud-initから実行される(cloud-initの設定は別途インスタンス作成時に行っている)
    {
      "type": "file",
      "source": "./scripts",
      "destination": "/home/hoge"
    },
    
    -- 上記のスクリプトに対して実行権限付与
    {
      "type": "shell",
      "inline": [
        "chmod +x /home/hoge/scripts/*"
      ]
    }
  ]

packer build でビルドしたimageがAWSに今回の共通で使用するAMIとして登録されます

EC2インスタンス作成

作成したAMIを元に、クライアントごとのインスタンスを作成します。なお、インスタンス作成は TerraformCloudFormationなどは使わず、AWS CLIを利用したスクリプトを作成して実行しています。

インスタンス作成スクリプトはこのような流れの処理となります。

インスタンス作成

以下のように、aws ec2 run-instances コマンドを使用し、Tagにクライアント識別IDを指定して作成しています。 ここで指定したクライアント識別IDを元にパラメータストアから自分用の環境変数を登録/取得したり、Private DNS用のドメインに使用します。

aws ec2 run-instances \
  --image-id ${AMI_ID} \
  --key-name ${KEY_NAME} 
  --region ${REGION} \
  --subnet-id ${SUBNET_ID} \
  --security-group-ids ${SECURITY_GROUP} \
  --user-data file://${USER_DATA} \
  --instance-type ${INSTANCE_TYPE} \
  --tag-specifications "ResourceType=instance,Tags=[{Key=ClientId,Value=${CLIENT_ID}}]" \
  --iam-instance-profile "Arn=${SERVICE_ROLE}"

user-data には初回起動時に実行したいスクリプト(Packerでビルド時にコピーしておいたスクリプト)を指定しているだけとなります。

#!/bin/bash
/home/hoge/scripts/bootstrap.sh

パラメータストアに環境変数登録

使用する環境変数は、Keyは共通ですが値がクライアントによって異なります。そのため、HOGEというKeyを使用する場合、<クライアント識別ID>.HOGE という形式でパラメータストアに登録しています。

(注. パラメータストアに階層やタグ付けがサポートされたらしく、このあたりの構成は今後見直す予定です)

登録は aws ssm put-parameter を実行します

aws ssm put-parameter \
  --name ${KEY} \
  --value ${VALUE}  \
  --type ${PARAMETER_TYPE} \  # String、SecureStringなど
  --overwrite

これでクライアントごとのEC2インスタンスが作成、起動されます。次にインスタンス起動時の流れについてです。

EC2インスタンス起動

起動時は、初回起動と毎回起動でそれぞれ以下のような処理を行います。

  • 初回: パラメータストアから自身に関連する環境変数を取得し、サーバアプリケーションのセットアップ
  • 毎回: 自身の内部IPをRoute53のPrivate DNSに登録/更新

内部IPは固定しておらず起動時に割り振られるため、毎回更新するようにしています。

それではそれぞれの内容について見ていきます。

パラメータストアから環境変数取得

登録時の内容で記載しましたが、環境変数<クライアント識別ID>.HOGEという形式で登録しています。そのため、まずは自身のクライアント識別IDを判定した後に必要な環境変数aws ssm get-parametersで取得します。

# 自身のインスタンスIDをメタデータから取得
INSTANCE_ID=$(curl -s http://169.254.169.254/latest/meta-data/instance-id)


# クライアント識別IDをインスタンス作成時に指定したTagから取得
# (describe-instancesのfilterに自身のインスタンスIDを指定)
CLIENT_ID_TAG=$(aws ec2 describe-instances \
  --region=${REGION} \
  --filters "Name=instance-id,Values=${INSTANCE_ID}" \
  | jq -r '.Reservations[].Instances[].Tags[] | select(.Key == "ClientId").Value'
)


# 環境変数を取得
# タイプをSecureStringにしている変数もあるため、一律 --with-decryption オプションを指定している
HOGE=$(aws ssm get-parameters \
  --name "${CLIENT_ID_TAG}.HOGE" \
  --with-decryption --region ${REGION} \
  | jq -r ".Parameters[].Value")


export HOGE=${HOGE}  

内部IPをPrivate DNSに登録

最後に、Proxy層からPrivate DNSで名前解決できるように自身のIPをRoute 53に登録してやります。

なお、Route53には事前に対象のHosted Zoneを Private Hosted Zone for Amazon VPCタイプとして登録しておきます。ここでは例としてDomain Nameをlocalとします。

EC2インスタンスから登録されるRecordSetは以下の形式とします。

  • Name: <クライアント識別ID>.local
  • Type: CNAME
  • Value: EC2インスタンスの内部IP

これらを行うスクリプト例は以下となります。

# 内部IPを取得
# (describe-instancesのfilterに自身のインスタンスIDを指定)
PRIVATE_IP=$(aws ec2 describe-instances \
  --region=${REGION} \
  --filters "Name=instance-id,Values=${INSTANCE_ID}" \
  | jq -r '.Reservations[].Instances[].PrivateIpAddress'
)


# Route53の登録先Hosted Zone IDを取得
# SEARCH_KEYは今回の例でいうと 'local.' になります
HOSTED_ZONE_ID=$(aws route53 list-hosted-zones \
  --region=${REGION} \
  | jq -r ".HostedZones[] | select(.Name == \"${SEARCH_KEY}\").Id"
)


# この後の登録コマンドで指定するための定義ファイル
# 毎起動時の登録用(IPが変わるため)に、Actionには 'UPSERT' を指定
RECORDSET_FILE="/tmp/create_recordset.json"
cat <<EOT > ${RECORDSET_FILE}
{
  "Changes": [
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "<クライアント識別ID>.local",
        "Type": "CNAME",
        "TTL": 300,
        "ResourceRecords": [
          {
            "Value": "${PRIVATE_IP}"
          }
        ]
      }
    }
  ]
}
EOT


# 作成した定義ファイルを指定し、Route53に登録
aws route53 change-resource-record-sets  \
  --hosted-zone-id ${HOSTED_ZONE_ID} \
  --change-batch file:///${RECORDSET_FILE}

実行するステップはやや多いですが、このような構成をとることでVPC内ではドメイン指定でのアクセスが可能となるため、IPを意識する必要がなくなるため柔軟な構成になるかと思います。

今回のまとめ

いまさらインスタンス立てるとかめんどくさいなぁ、、、とか思いながら色々調べて構築しましたが、EC2まわりのサービスも増えてるんだなぁ、なんて感じました(特にパラメータストアはとても便利)

パラメータストア以外にもSystems ManagerにはRun CommandやPatch ManagerなどEC2インスタンスを管理する上でとても便利な仕組みが揃っていますのでこのあたりも導入していきたいと思います。

余談ですが、Systems Managerの存在はre:Invent 2016で発表された時から名前だけは知ってましたが、今回の対応するまでずっとオンプレ専用のサービスだと勘違いしてて記憶から消えかけていました。。。

最後に

前編をProxy層(Nginx)、後編をApp層(EC2)について書かせていただきましたがいかかだったでしょうか。 そもそもの要件自体がけっこう特殊だったりもするので、なんでこんな構成に?みたいなとこもあるかも知れませんが、どなたかの参考になれば幸いです。もう少し聞いてみたい、というかたはwantedlyの「話を聞いてみたい」ボタンからどうぞ。

※前編をあらためて読みたい方はこちらからどうぞ developer.medley.jp

お知らせ

メドレーでは、医師たちがつくるオンライン医療事典「MEDLEY」、オンライン診療アプリ「CLINICS」、医療介護の求人サイト「ジョブメドレー」、口コミで探せる介護施設の検索サイト「介護のほんね」などのプロダクトを提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。

メドレーで一緒に医療体験を変えるプロダクト作りに関わりたい方のご連絡お待ちしております。

www.medley.jp

クライアント認証とPath Based Routingが必要なサーバをAWSで構築(前編:Proxy層)

今回の内容について

メドレー開発本部の田中です。 先日、Proxy層をElastic Beanstalk上のNginxで、App層をEC2インスタンスで構築する機会がありました。ここだけ見るととても普通に見えますが、後述する制約から苦労した点もあり、制約を乗り越えるための工夫も含めてお話できる限り共有させていただきます(先にお伝えしておくと、特殊な事情がなければ今回のようなケースではALBで対応するECSサービスにPath Based Routingしてやるのが良いと思います)。

技術要素として、Nginx(OpenResty)/ Route53 Private DNS / EC2 / Systems Manager パラメータストア あたりに触れたいと思います。(BeanstalkはMulticontainer Dockerを使用し、それも慣れるまでちょっとクセあったなぁと思ったのですが分量が多くなりそうなのでまた別の機会に共有させて頂きます)

まず前編としてProxy層、主にNginxを使用したDynamic Path Based Routingについてお話して、後編はApp層について、EC2とSystems Manager パラメータストアあたりについて共有させていただければと思います。

設計/構築する上での前提と方針

対象となる案件を進める上での要件・制限内容は諸事情あり、ざっとまとめるとこのような感じです。

  • 環境はAWSを使用する
  • サーバアプリケーション、クライアントアプリケーションはユーザ毎で、サーバアプリケーションは共用できない(ユーザが増える度にクライアント/サーバのセットが増えるイメージ)
  • ただし、クライアントからの接続先となるEndpointは同じだが、Host Based Routingは訳あって利用できない
  • クライアント認証を使用する

上記から、以下の設計方針で進める事にしました。

  • Proxy層でクライアント認証を行い、Path Based Routingで対象となるサーバにリクエストをproxyする。Path部分にクライアント別の識別IDを含め、その値を元にPrivate DNSで名前解決する
    • 例) https://example.com/a-client/api => http://a-client.local/api
    • App層は個別EC2インスタンスとする

設計する上で悩んだ点

主に2点ありますが、まずはProxy層です。出来るだけAWSのマネージド・サービスで済ませたかったので、クライアント認証とPath Based Routingが可能でやりたい事に合うかどうか調べましたが以下の理由で断念し、普通(?)にELB + Nginxを利用することにしました。

  • ALB: クライアント認証に非対応。またSSL終端となるのでNginx側でクライアント認証が出来ない
  • API GW: クライアント認証は対応しておりRouting部分もがんばればいけるかも?、と思ったがProxy先が動的に増えたリするので管理ふくめ難しそうであった

次にApp層の構成をどうするかでした。集積度を高めるためにコンテナ利用も検討したのですが、使用するアプリケーションの必要スペックや要件などからいまいちフィットせず、個別のEC2インスタンスにすることにしました(今でももっと良い方法がないか悩んでたりします)

全体構成

出来上がった全体構成のイメージは以下となります。なお台数は実環境と異なり、今回の内容と関係ない部分などは省略しています。

f:id:medley_inc:20170823180401p:plain

次に、今回の本題となるProxy層の構成について触れたいと思います。

Proxy層の構成

Proxy層の方針等はまとめると以下の通りで、proxy先の動的判定と名前解決する箇所がキモとなります。

  • App層のインスタンスは、起動時に自身の内部IPとTagに設定したクライアント識別IDを元にRoute53のPrivateDNSに登録する
    • クライアント識別IDがa-clientの場合、a-client.localのように登録
  • Proxy層のNginxはクライアント認証を行い、リクエストパスから取り出したクライアント識別IDを元に転送先Endpointを生成し、backendにproxyする
    • App層のインスタンスは動的に増えるため、リクエスト時に名前解決したい(インスタンスが増える度に自動でNginxのconfを編集することも検討したが追加数が読めず、confがふくれあがるのもなぁ、、、という思いがあり止めました)

Nginxはbackendが増えても起動しっぱなしで動的に名前解決して動作させたかったため、lua-nginx-moduleを導入し balancer_by_lua ディレクティブと lua-resty-dns モジュールを使用することとし、構築の手間の関係からOpenRestyを導入することにしました。

lua-nginx-moduleを使用したconfファイル

confファイル全体としては以下となります(関係ない箇所は省いています)。ポイントと記載した部分についての説明は後述します。

http {
    upstream app {
        # ポイント1. 
        # Private DNSで設定したIP(CNAMEに設定)を元に動的Routing
        balancer_by_lua_block {
            local balancer = require "ngx.balancer"


            local host = ngx.ctx.upstream_server.cname
            local port = '8888'


            local ok, err = balancer.set_current_peer(host, port)
            if not ok then
                return ngx.exit(500)
            end
        }
    }


    server {
        listen 443 ssl;


        set $proxy_upstream_host '';
        set $proxy_upstream_domain '.local';


        location ^~ /api/ {
           rewrite_by_lua_block {


               -- pathからクライアント識別IDを取得し、Private DNSに設定したドメインを生成
               -- https://example.com/<id>/api という形式のリクエストから、<id>.local というドメインを生成して
               -- ngx.var.proxy_upstream_host変数に格納
               local ngx_re = require "ngx.re"
               local res, err = ngx_re.split(ngx.var.request_uri, "/", nil, {pos = 0})
               local id = res[3]
               ngx.var.proxy_upstream_host = id..ngx.var.proxy_upstream_domain;


               -- resolver設定
               local resolver = require "resty.dns.resolver"
               local r, err = resolver:new{
                   nameservers = {{"x.x.x.x", 53}}, -- 使用するnameserver
               }
               if not r then
                   ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
               end


               -- ポイント2.
               -- 生成したドメイン名(<id>.local)を元に名前解決し、取得した結果をngx.ctxにセット
               --  (balancer_by_lua_blockで使用する)
               local answers, err = r:query(ngx.var.proxy_upstream_host, { qtype = r.TYPE_CNAME })
               if not answers then
                   ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
               end
               if answers.errcode then
                   ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
               end
               ngx.ctx.upstream_server = answers[1]
           }


           proxy_set_header Host $host;
           proxy_set_header X-Real-IP $remote_addr;
           proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
           proxy_set_header X-Forwarded-Proto $scheme;


           -- http://<id>.local/api に proxy
           rewrite ^/api/(.+)$ /api/ break;
           proxy_pass http://app;
        }
    }
}

ポイント1. 動的Routing

balancer.set_current_peer にてproxy先を動的に設定します。

host 部分にはドメインを直接指定することができないため、ポイント2. で ngx.ctx にセットしたDNSの値からIP(Route53にCNAMEレコードとして設定している)を指定しています。

balancer_by_lua_block {
    local balancer = require "ngx.balancer"


    -- ngx.ctxにセットしていた、Private DNSから取得した内部IPをセット
    local host = ngx.ctx.upstream_server.cname
    local port = '8888'


    -- proxy先セット。hostにドメインは直接指定できない
    local ok, err = balancer.set_current_peer(host, port)
    if not ok then
        return ngx.exit(500)
    end
}

詳細についてはOpenRestyのドキュメントを参照してください

ポイント2. 動的名前解決

r:query にて、生成したドメイン名(.local)を問い合わせます。r 部分は resolver:new でnameserverを指定したresolverとなります。 なお、nameserverに指定するIPは今回はRoute53のPrivate DNSを指定するため、外部nameserverではなくローカルのnameserver(10.0.0.2など)を指定することになります。

問い合わせ結果のanswers部分はLua table形式の配列となります。今回の例でいうと対象は1件となるので、その値をbalancer_by_lua_blockで使用するためにngx.ctxにセットしています。

local answers, err = r:query(ngx.var.proxy_upstream_host, { qtype = r.TYPE_CNAME })
if not answers then
    ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
if answers.errcode then
    ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
ngx.ctx.upstream_server = answers[1]

詳細についてはOpenRestyのドキュメントを参照してください

今回のまとめ

upstream先を動的に判定してproxyするという要件はそうそう無いかもしれませんし、途中までは複雑な構成になりそうだなぁとドキドキしてしましたが、結果としてはそれなりにシンプルになったかなと思います。今更ながらNginx(とlua module)は柔軟で良く出来てるなぁという感想でした。

後編はApp層について、EC2とSystems Manager パラメータストアあたりについて共有させていただければと思います。

developer.medley.jp

参考リンク

構築にあたり、下記記事を参考にさせていただきました。ありがとうございます。

qiita.com

お知らせ

メドレーでは、医師たちがつくるオンライン医療事典「MEDLEY」、オンライン診療アプリ「CLINICS」、医療介護の求人サイト「ジョブメドレー」、口コミで探せる介護施設の検索サイト「介護のほんね」などのプロダクトを提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。

メドレーで一緒に医療体験を変えるプロダクト作りに関わりたい方のご連絡お待ちしております。

www.medley.jp

NDBオープンデータをオープン化してみた話

開発本部の平山です。先日、社内勉強会「TechLunch」にて社外に公開できない内容の発表をしてしまいましたので、その代わりとして、厚生労働省が提供する「NDBオープンデータ」をオープン化した話について、ブログを書こうと思います。 

NDBオープンデータとは?

www.mhlw.go.jp

作成の背景

◆ レセプト情報・特定健診等情報データベース(NDB)は、悉皆性が高いレセプト情報、および検査値などの詳細な情報を有する特定健診等情報が含まれており、国民の医療動向を評価するうえで有用なデータだと考えられている。

◆ 2011年度より、医療費適正化計画策定に資する目的以外でのNDBデータの利用が認められたが、NDBデータの機微性の高さに鑑み、利用者に対しては高いレベルのセキュリティ要件を課したうえで、データ提供が行われてきた。

◆ 一方で、多くの研究者が必ずしも詳細な個票データを必要とするわけではないため、多くの人々が使用できるような、あらかじめ定式化された集計データをNDBデータをもとに整備することが重要ではないか、という議論が有識者会議等でなされてきた。

◆ NDBの民間提供に関する議論でも、「レセプト情報等の提供に関するワーキンググループ」からの報告では、汎用性が高く様々なニーズに一定程度応えうる基礎的な集計表を作成し、公表していくことがむしろ適当である、という指摘がみられた。 

作成の目的

◆ 多くの人々がNDBデータに基づいた保健医療に関する知見に接することが出来るよう、NDBデータを用いて基礎的な集計表を作成したうえで、公表する。

◆ NDBデータに基づき、医療の提供実態や特定健診等の結果をわかりやすく示す。

 

要は皆さんが、病院に行った時にもらう明細書に記載されている初診〇〇点、外来診療料〇〇点のようなデータが個人情報が匿名化された状態で収集しその統計データを一般に公開する、といったところでしょうか。


このようなデータがオープンになっていることはとても意義のあることだと思いますし、公開にまでこぎつけた関係者の苦労が想像されます。しかし、このような画期的なデータ提供ではありますが、Excelファイルでの提供となっており、かつ加工がしづらいデータ構造になっているため、データを細かくみてみようとすると非常に手間がかかるという問題があります。

 

NDBオープンデータのオープン化

そこでNDBオープンデータとして公開されているExcelファイルを加工し、DBに格納しBIツール(Redash)から参照させるようにしてみました。

1. データ加工 & DB取り込み

公開サイトにある医科診療行為に関するExcelファイルを取得し、ログテーブルとしてよくあるフォーマットに変換しDBに取り込む。

変換前

f:id:medley_inc:20170721150757p:plain

変換後

*************************** 1. row ***************************
id: 1
practice_category_code: A000
practice_category_name: 初診料
practice_code: 111000110
practice_name: 初診
practice_type: 外来
target: all
revision: 2014
prefecture:
sex:
age:
score: 251700771
created_at: 0000-00-00 00:00:00
updated_at: 0000-00-00 00:00:00
*************************** 2. row ***************************
id: 2
practice_category_code: A000
practice_category_name: 初診料
practice_code: 111000110
practice_name: 初診
practice_type: 外来
target: sex_age
revision: 2014
prefecture:
sex: 男性
age: 0~4歳
score: 13158090
created_at: 0000-00-00 00:00:00
updated_at: 0000-00-00 00:00:00
*************************** 3. row ***************************
id: 3
practice_category_code: A000
practice_category_name: 初診料
practice_code: 111000110
practice_name: 初診
practice_type: 外来
target: sex_age
revision: 2014
prefecture:
sex: 男性
age: 5~9歳
score: 12444947
created_at: 0000-00-00 00:00:00
updated_at: 0000-00-00 00:00:00

2. データの参照

変換したデータを取り込んだDBをRedashから参照。分析したいデータを取得するためのクエリを書いてダッシュボード化。

 ※ お試し環境を構築したので興味のある人はさわってみてください(@gmail.comGoogleアカウントでログインできます)。

 

お試し環境(期間限定) -  http://sandbox.medley.jp/dashboard/ndb

f:id:medley_inc:20170721153210p:plain

f:id:medley_inc:20170726125246p:plain

NDBオープンデータの活用例

以下に簡単なデータ活用のサンプルを載せました。医薬診療行為だけでなく特定健診や薬剤のデータを使うともう少し面白い気付きがあるかもしれません。

いずれにせよ、このように加工可能な形でのデータ提供こそがオープンデータ提供の価値だと思うので、このような仕組みが加速すれば良いなと思います。

0-4歳 男性 診療行為点数

f:id:dev-medley:20170817101900p:plain

90歳以上 男性 診療行為点数

f:id:dev-medley:20170817101934p:plain

140023350 胃瘻より流動食点滴注入 都道府県別

f:id:dev-medley:20170817102021p:plain

150086210 角膜移植術 年齡別

f:id:dev-medley:20170817102100p:plain

まとめ

以上、NDBオープンデータをオープン化してみた話について書いてみました。

このように*.go.jp から提供されるデータは一般的にExcelやPDFでのファイル提供が基本で、インターネットサービスのようにAPIのような形で提供されることはありません。せっかく貴重なデータが提供されているにも関わらず、それがITシステムと連動しづらいことで、活用されない状況になっているのはとても残念なことに思います。Code for Americaの事例ではないですが、もっとインターネット系の人材がこのような取り組みに入り込んでいくようになれば、より合理的でスマートな仕組みが加速し、業界全体のIT化も加速するのではないでしょうか。

お知らせ

メドレーでは、医師たちがつくるオンライン医療事典「MEDLEY」、オンライン診療アプリ「CLINICS」、医療介護の求人サイト「ジョブメドレー」、口コミで探せる介護施設の検索サイト「介護のほんね」などのプロダクトを提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。

メドレーで一緒に医療体験を変えるプロダクト作りに関わりたい方のご連絡お待ちしております。

www.medley.jp

デザイン言語システムを入れたらコミュニケーションコストがぐっと下がった話〜メドレーTechLunch〜

ビールが美味しい季節ですね!

最近飲みすぎて嫁に叱られて、飲み会自粛中のデザイナー・マエダです。

メドレーではTechLunchという社内勉強会を実施しているのですが、デザインについて私も発表する機会をいただきましたので、その内容を紹介させていただきます。テーマは「DLSの導入について」です。発表資料は記事の最後をご覧ください。

DLS(デザイン言語システム)とは

DLSとはDesignLanguageSystemの略で、すごい単純にいえばデザインガイドラインみたいにUIに一貫性をもたせるため、配色やレイアウト、タイポグラフィやマージンなどのルールを策定するものです。

私が主に担当しているオンライン診療アプリ「CLINICS」は、iOSAndroid、Webと3つのプラットフォームで運用しているのですが、入社した当初はプラットフォーム毎に違ったUIやルールで開発しており、サービスとして一貫性のあるサービス体験を提供できるとは言えない状況でした。また新たに機能を追加する際、それぞれ違なるデザインをしなければならず、デザイン作業においても負荷がかかっていました。

DLSが必要な理由

CLINICSではプラットフォームごとに異なるUIを提供していたため、一貫したサービス品質をユーザーに提供できていないこと、開発者ごとにUIに対して認識にズレが生じていたことが課題でした。それに伴い開発速度も決して速いとは言えず、どうにか一定の品質を担保しつつ開発スピードも改善できないかと悩んでいたところ、Airbnbの開発者ブログでAirbnb Design Systemという記事を見かけました。

これまでのデザインガイドラインは単にカラーやマージンの定義を取り決めるだけだったのですが、Airbnbではデザイン言語として定義し、他の言語と同じようにチームと共有し、エンジニア・デザイナー同士で理解できる設計を作り上げているといった内容でした。

※参考

medium.com

私がデザイナーとしてサービスデザインに携わる重要な役割のひとつとして、単にデザインをするだけでなくデザインを通してUI設計の制約をつくり、継続的に運用しやすいプロダクトに仕上げることがあると思っています。

DLSを起点として、各プラットフォームの開発者が共通の認識でシステム開発が行えれば、これまで以上にスピーディが開発が行え、一貫性のある体験をユーザーに提供できるプロダクトにできるはずだと考え、CLINICS独自のDLSを開発することにしました。

f:id:dev-medley:20170725121754p:plain
CLINICSのDLSの一部

 

DLSを導入してどうなったか

CLINICSでは、AndroidiOS、webで担当するエンジニアが異なるのですが、DLSを設計しシステムの詳細を共有することで、UIに対しての共通認識が生まれ、一貫した品質を担保できるようになりました。さらにこれまでエンジニアだけでデザインを考えて悩んでいた時間を、コンポーネント設計で組み立てたデザインをDLSで定義したことで、UIに悩むことなく機能ロジックに重点を置いた開発に専念できるようになったのではないかと思います。

デザイン言語でサービス設計の基礎を築けたことで、開発だけに追われていた状況から、より良いサービスを作るためにはどんなUXをユーザーに提供すべきかという声がエンジニアからも頻繁に声が上がりはじめたことも良かった点です。

f:id:dev-medley:20170725122157j:plain
エンジニア陣がUIについて熱い議論を交わしている様子

TechLunchでは、こうした内容について実際に作ったコンポーネント設計なども見せながらお話しました。発表資料はこちら。

speakerdeck.com

まとめ

DLS導入以前は、エンジニアが開発に追われていたということもあり、プラットフォーム毎に議論ができていなかったエンジニアがお互いの担当プラットフォームを意識したコミュニケーションがとれるようになったことは予想外に良かった点です(別に仲が悪かったとかそういうことではなくw)。

さらにDLSでUIの基盤をつくったことで、デザイナーが手を動かさずともコンポーネントの組み合わせを話し合うだけで、エンジニア完結で一貫した品質で機能実装できるようになりました。これにより私も次の施策やプロジェクトに専念できるようになり、効率的に仕事ができるようになりました。

まだデザインルールに一貫性がないプロジェクトを担当しているデザイナーやエンジニアは、ぜひDLSの導入を検討してみてはいかがでしょうか。
単にデザイン品質だけでなく、チームコミュニケーションも改善されるとおもいます。

より詳しく話を聞きたいかたは、気軽に「話を聞きに行きたい」をクリックしてみてください。ビールを飲みながらデザイン談義をしましょう!(嫁に怒られない程度にw)

 

メドレー開発本部について、もっと詳しく知りたい方はこちらからどうぞ。

www.medley.jp

www.wantedly.com

元フロントエンドエンジニアから見たAndroid開発

今回の内容について

みなさん、こんにちは。開発本部でオンライン診療アプリ「CLINICS」の開発を担当している平木です。

弊社では、インフラ・サーバ・フロントで役割を区切らず、全ての開発メンバーが必要に応じてスキルを広げながら開発に取り組んでいます。 自分も入社前はフロントエンド専門のエンジニアでしたが、入社後はそれに加えてRailsを使ったサーバサイドの開発や、Swiftを使ったiOSアプリ開発、 そして、現在メインにやっているAndroid開発と一通りのプラットフォームや言語を使って開発するようになっています。

エンジニアが自身のスキルを広げる場合、自分の経験や知識を応用して、新しいプラットフォームを理解していくということが多いと思います。

元フロントエンジニアの経験を持っている自分がAndroid開発に関わってみて やりやすかった部分つらかった部分などを書いていこうと思います。同じような立場でこれから開発しようと思う方には少しお役に立てるかもしれません。

やりやすかった部分

Android Studioのありがたさ

f:id:dev-medley:20170727194320p:plain

いきなりIDEの話になっていますが。やはりAndroid Studioのオールインワン感がとても便利です。Webフロントエンドの開発だとIDEといえば WebStormなど使ったことがありましたが、ライブラリを含めた補完周りなどはやっぱりJavaなどでの開発した方がIDEは力を発揮しやすいんではないかと思いました。

とりあえずほとんど設定をいじらなくても、補完やドキュメントの参照ができるのはラクです。またJSなどで変数に変換してもあんまり便利さが湧きませんが、 Javaの場合だと勝手に対応した型もつけてくれたりと至れり尽くせりだと思いました。

ゲッターセッターなども手作業でやると単純作業になりますが、⌘Nで勝手に展開してくれますし、Javaでの開発の冗長な部分をあまり感じないで開発できるのが凄いです。

エミュレータなども自分が使い始めたときは既に起動も高速な感じでしたし、エミュレータの管理などもAndroid Studio内で完結するのであまり迷ったりすることがないです。また初心者に優しいなと思ったのが、Activityなど作るのにテンプレートがちゃんと用意されてるのであまりファイル構成が分かってない初めの頃でもちゃんと画面を作ることができるところです。

ビルドシステムが分かりやすい

f:id:dev-medley:20170727194345p:plain

ご存知Gradleですが、通常の使い方してる分には(あまりGradleでがんばらなければ)、WebPackとかよりも書きやすいよねと思います。もちろん目的が違いますが。

フロントエンド開発でよく陥りがちな「設定ファイルなんだかプログラムなのか分からないくらい作られた」というような設定ファイルにはなりにくいんじゃないかと感じています。

build.gradle で設定した環境変数などもソースからさっと使うことができますし、良く考えられてるなーと思いました。

公式のライブラリであれば、 build.gradle でlintとしてライブラリの更新があることが分かるのも親切です。サードパーティのライブラリもこれがされてるととても嬉しいんですが、 Analyze -> Run Inspection by Name -> Newer Library Versions Available で見てくれるんでガマンしてます。

View部分の作り方がフロントエンドに似ているので取っつきやすい

これはHTML / CSS を書いていた人間からすると大変に敷居が低いと思いました。

使うパーツは最初はどんなものがあるのか分からないのでドキュメント引いたり、 Design タブからパーツを選択したりしてViewを作ってましたが、HTMLを組む感覚でガンガンと作っていけました。

f:id:dev-medley:20170727195637p:plain

基本的にはHTMLタグのなかで style 属性付けていくみたいな感じです。margin とか padding とかも同じですし、textAlign なんかもあるので初期のころは本当に助かりました。

もちろん AppBarLayout とか Toolbar とか Theme などAndroid独自の概念はありますがそういった差分的なものは知識として覚えていけば良いだけなので、フロントエンドに慣れていると大変にView作るのがラクだと感じます。

iOSのStoryBoardは未だにつらい。

ググったときに古めの情報を参考にしても問題が解決できる後方互換性の高さ

ここ数年フロントエンドの技術的な流れが早すぎるというのは一般的に良く言われていますが、良い意味でAndroidは古い情報もちゃんと使えるというのはとても助かりました。

一番古いので2011年くらいの情報だったら使えました。(もちろん廃止されてる…というのも多いのですが)

初めのうちは フロントエンドだとこうやるけどAndroidではどうするんだろう というような疑問が多いと思うのですが、特にViewに関わるような問題などは古い記述でも問題なく使える後方互換性の高さのおかげで「ググったように書いたのに、今は使えない…」というストレスからかなり開放されます。

つらかった部分

ライブラリや画像を気軽に入れにくい

これついては、フロントエンドのJSなどでも別に気軽にさくさくとライブラリを入れていたわけではないですが、いざ入れようとするとAndroid(というよりもネイティブアプリ全般か)の場合全てAPKの容量増加という自体に直結するので中々気をつかいます。

さすがにゲームでもないのに容量重いアプリにするとそもそもダウンロードしてもらえるかどうかも怪しくなります。パッケージする画像なども多用するような仕様にしないようにしています。

あと最初のころにビルドできずに困るわ…と思って必死に対応を調べた 64K 参照制限 も一回設定してしまえば何てことはないんですが、割と初見殺しだと思います。

また実際にプロダクション用にビルドするとなるとフロントエンドでもおなじみのソースの難読化をするのですがその際にライブラリのなかで難読化しちゃいけないなどの指定をしないと余裕で動かなくなります。

ということで各ライブラリを使うときにはProGuardの設定をしないといけないのですが、これも気軽にライブラリ使おうと中々ならない理由でした。

バッググラウンドプロパティ関連の貧弱さ

良い点のところでHTML / CSS とViewの作りが似ているので幸せと書いたんですが、1つだけ許せないことがありまして、それが バックグラウンドプロパティ関連のプロパティの貧弱さ というものです。

どういうことかというと…例えばこんな感じのラベル作るとします。

f:id:dev-medley:20170727194414p:plain

HTML / CSSだったら楽勝だと思います。

<span class="radius">診察待ち</span>
.radius {
  border-radius: 12px;
  padding: 4px 12px;
  background-color: #89C8BA;
  color: #FFFFFF;
}

しかしAndroidの場合は

<TextView
    android:id="@+id/upcoming_list_status"
    android:textSize="14sp"
    android:layout_width="100dp"
    android:layout_height="wrap_content"
    android:textAlignment="center"
    android:background="@drawable/label_status_upcoming"
    android:textColor="#FFFFFF"
    android:text="診察待ち" />

とテキスト作ったうえで、 background の指定先のベクターアセットを作ってやらないといけません。 このアセットが paddingbackground-color など指定されているというものになります。

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape android:shape="rectangle">
            <stroke android:width="1dp" android:color="@color/label_status_upcoming" />
            <corners android:bottomLeftRadius="12dp" android:bottomRightRadius="12dp" android:topLeftRadius="12dp" android:topRightRadius="12dp" />
            <solid android:color="@color/label_status_upcoming" />
            <padding android:left="12dp" android:right="12dp" android:top="2dp" android:bottom="2dp" />
        </shape>
    </item>
</selector>

とても面倒です。

リストがつらい

配列になっているデータを取り出してリストとして表示するという状況、良くあると思います。 フロントエンドなら <ul/> で囲んだなかの <li/> にデータを forEach() やら map() やら使って作っていくのが常道ですが、Androidは手順が多いので面倒です。

f:id:dev-medley:20170727194509p:plain

このようなお知らせのリストを作るのに…

public class NotificationItemHelper {
    private String message;
    private String link;
    private String patientName;

    public NotificationItemHelper(String message, String link, String patientName) {
        this.message = message;
        this.link = link;
        this.patientName = patientName;
    }

    public String getMessage() {
        return message;
    }

    public String getLink() {
        return link;
    }

    public String getPatientName() {
        return patientName;
    }
}

リストに表示するためのクラスを作ってあげて、

public class NotificationListAdapter extends ArrayAdapter<NotificationItemHelper> {

    private int layoutResource;

    public NotificationListAdapter(Context context, int resource, List<NotificationItemHelper> objects) {
        super(context, resource, objects);
        this.layoutResource = resource;
    }

    @NonNull
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View view = convertView;

        if (view == null) {
            LayoutInflater layoutInflater = LayoutInflater.from(getContext());
            view = layoutInflater.inflate(layoutResource, null);
        }

        NotificationItemHelper notificationItemHelper = getItem(position);

        if (notificationItemHelper != null) {
            TextView message = (TextView) view.findViewById(R.id.notification_list_message);
            TextView patientName = (TextView) view.findViewById(R.id.notification_list_patient_name);

            if (message != null) {
                message.setText(notificationItemHelper.getMessage());
            }

            if (patientName != null) {
                patientName.setText(notificationItemHelper.getPatientName());
            }
        }

        return view;
    }
}

それをViewに表示させるためのAdapterというものを作り、

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/upcoming_list"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingStart="@dimen/spacing_small"
    android:paddingEnd="@dimen/spacing_small"
    android:orientation="horizontal">

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:paddingTop="@dimen/spacing_small"
        android:paddingBottom="@dimen/spacing_small"
        android:orientation="vertical">

        <TextView
            android:id="@+id/notification_list_message"
            style="@style/TextBoldStyle"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="@dimen/spacing_xsmall" />

        <TextView
            android:id="@+id/notification_list_patient_name"
            style="@style/TextNormalStyle"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="@dimen/spacing_xsmall"
            android:textColor="@color/text_color_secondary" />
    </LinearLayout>

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="end|center_vertical"
        android:src="@drawable/ic_keyboard_arrow_right_black_24dp" />
</LinearLayout>

実際のリストの内容をxmlで作ってあげて、

<ListView
    android:id="@+id/notification_list"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

リストのラッパーを指定して

List<NotificationItemHelper> notificationItemList = new ArrayList<>();
ListView notificationListView = findViewById(R.id.notification_list);
List<NotificationResponse> notifications = notificationsResponse.getNotificationResponses();
for (Notification notification : notifications) {
    NotificationItemHelper item = new NotificationItemHelper(
            notification.getMessage(),
            notification.getLink(),
            notification.getPatientName());
    notificationItemList.add(item);
}

NotificationListAdapter notificationListAdapter = new NotificationListAdapter(this, R.layout.notification_layout, notificationItemList);
notificationListView.setAdapter(notificationListAdapter);

ここまでやってようやくリストが表示されます。未だに面倒だなーと思います。

最後に

色々と細かい例を上げましたが、いかがでしょうか。

今回は書いてませんが、ライブラリもJSとは考え方違って面白いなあと思うものがあります。特にViewパーツを簡単に選んだり、イベントを書くことができるButterKnifeやバリデーションライブラリのAndroid SaripaarのようにJavaアノテーションを付けるだけで簡単に使える部分などはJavaに触れてこなかった身としては新鮮でした。

全体的な印象としてはプラットフォーム特有のクセなどはもちろんありますが、フロントエンドエンジニアでもかなり取り組みやすいのではないかと感じています。何よりAndroid実機で自分が作ったものが動いてるのを見るとブラウザで自分が作ったサイトが動いているというのとは一味違った達成感があります。

もちろん開発していく内に、たとえばJSにはないスレッドの概念が立ち塞がったりAndroid特有のライフサイクルに悩まされたりもしますが、公式のドキュメントも充実していますし、Stack Overflowなど見て解決することがかなり多いです。

これを読んでAndroid開発やってみようと思っていただけたら良いなと思います。 もっといろいろ知りたいという方は「話を聞いてみたい」ボタンを押してもらえれば!

お知らせ

メドレーでは一緒に働くエンジニア・デザイナーを募集しています。

www.wantedly.com

www.medley.jp

WebPushAPIを用いたブラウザでのプッシュ通知開発 〜メドレーTechLunch〜

※本投稿はオフィシャルブログから転載したものです。元記事はこちら。

info.medley.jp

 

開発本部の宮内です。

先日、社内勉強会「TechLunch」にてWebPushAPIについての発表を行いましたので、その紹介をさせていただきたいと思います。

WebPushAPIとは?

一般的にプロダクトにおいて、スマートフォンアプリのような「プッシュ通知」を導入しようと思った場合、いままでは専用のアプリケーションを開発する必要がありました。

しかし、プッシュ通知に関するAPIW3Cで標準化が進み(まだドラフト状態とはいえ)、Google Chromeのバージョン42、Mozilla Firefoxのバージョン44に、WebPushAPIが導入され、元来あるWebアプリケーションに簡単にプッシュ通知を取り込むことが可能になりました。
詳しくは ウェブアプリへのプッシュ通知の追加 Using the Push APIや、WEB+DB PRESS Vol.97 などを読むとより理解が深まるので興味ある方はぜひ読んでみてください。

TechLunchでやったこと

TechLunchでは、自分が実際に実装したWebPushAPIのサンプルコードを元に、動作を示しながら挙動を開発本部の全員で追っていきました。

speakerdeck.com

具体的な手順などもスライドにありますが、公開したものだけだとちょっと分かりにくいかもですね。ごめんなさい。

もっと詳しく聞きたいなって人がいたら、気軽にここらへんにある「話を聞きに行きたい」ボタンを押下してみてください。

お知らせ

メドレーでは、医師たちがつくるオンライン医療事典「MEDLEY」、オンライン診療アプリ「CLINICS」、医療介護の求人サイト「ジョブメドレー」、口コミで探せる介護施設の検索サイト「介護のほんね」などのプロダクトを提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。

メドレーで一緒に医療体験を変えるプロダクト作りに関わりたい方のご連絡お待ちしております。

www.wantedly.com

www.wantedly.com

www.medley.jp