Medley Developer Blog

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

Rails Developers Meetup 2018 Day 3 Extremeで弊社の宍戸が発表させていただきました

こんにちは、開発本部の平木です。

去る7/14(土)にRails Developers Meetup 2018 Day 3 Extremeというイベントが開催されまして、弊社の宍戸電子カルテとセキュリティガイドラインAWSと私というタイトルで発表させていただきましたので、レポートさせていただきます。 f:id:medley_inc:20180719114338j:plain

発表のきっかけ

昨年に引き続きRubyKaigi 2018で弊社CTOの平山がLTスポンサーとして発表させていただいたり、ブースに出展をしていたことがきっかけになり、Rails Developers Meetupの主催者である平野さん(@yoshi_hirano)から平山へ出演のお声がけをしていただきました。イベントからイベントへ関係がつながるのはとても嬉しいですね。

発表テーマについて

せっかく、お声がけしていただいたので、発表テーマも弊社の特色が出るものが良いであろうと決まったのが今回のテーマです。

4月に発表した電子カルテシステムCLINICSカルテですが、何度かこちらのブログでもエントリが上がっていますとおり、電子カルテというシステムを構築する上で、セキュリティはかなり重要なウェイトを占める要素になっています。

このセキュリティを担保するための指針として3省4ガイドラインという総務省経産省厚労省の各省庁から出ている4種類のガイドラインが出ています。

これを実際にAWSをベースとしたシステムを構築する際の一例としてCLINICSカルテでの構築例を発表しようということになりました。

発表スライド

当日の発表スライドはこちらになります。

speakerdeck.com

会場ではスライドの色見がすっごく色褪せしていたりしましたが、発表した中でのAWSのサービスは会場のみなさんの中でも、あまり馴染みがないものが多いようでスライドの写真など撮られている方もいらっしゃって、知見共有という意味で参考になった模様です。

やはりクライアント認証に関しては普段サービス開発ではそこまでは使われないので、興味を持っていただいたようで良かったです!

まとめ

弊社のような医療業界ではなくても、フルマネージドサービスでセキュリティを意識するような場面があればご参考になるかもしれないテーマをエンジニアの宍戸から発表させていただきました。

社内で貯まった知見で機会があれば、ぜひ公開していきたいと思います。

メドレーについて気になった方は、こちらからどうぞ。

www.wantedly.com

Rubyを使ってHPKIカードのデータを読み取る

こんにちは、開発本部の宮内です。今回、HPKIカードについて調査を行いましたので、それについて書きます。

tl;dr

JAHIS HPKI 対応 IC カードガイドライン Ver.3.0を参考にして、HPKIテストカードから実際に公開鍵証明書を取得しました。

今後もHPKIについて調査を続行していきたいと思います。

HPKIとは?

HPKIとは厚生労働省が認める医療福祉関係資格(医師・薬剤師・看護師など26種類の保健医療福祉分野の国家資格と、院長・管理薬剤師など5種類の管理者資格)を認証することができるPKIです。

配布されたHPKIカードには、ルートCA、中間CA、証明書が格納されています。

このカードは、電子署名などに使用することができ、今後普及していけば、医療文書(処方箋や病院への紹介状など)を印刷、押印、送付するなどの非効率な業務をすることなく、すべてデジタル化することができるようになります。

また、電子認証用の証明書も含まれているため、認証・認可処理にも使用することができます。

今回、HPKIテストカードを用いて調査を行いました。 f:id:medley_inc:20180712151907j:plain

調査環境

  • macOS v10.13.5
  • ACR39-NTTCom
  • Ruby v2.5.1
  • smartcard v0.5.6
  • HPKIテスト用カード

PC/SC

HPKIカードのようなICカードとやり取りを行うには、PC/SCというAPI仕様を使う必要があります。

PC/SCはもともとWindows環境のみで利用可能でしたが、pcsc-liteというOSS実装があり、現在では様々なUNIX like OSでも利用できます。

macOSの場合、/System/Library/Frameworks/PCSC.framework/PCSCにライブラリが用意されており、特に準備する必要なく利用可能です。(2018年07月現在)

ただし、ICカードリーダーのドライバーをインストールする必要があります。

今回利用したACR39-NTTComダウンロードページmacOS v10.13に対応したドライバーが配布されていなかったため、ICカードリーダーのチップメーカーであるACS社のダウンロードページからドライバーを入手しました。

smartcard

検証する際に使用したgemはsmartcardです。 普通のrubygemと同じくgem installして利用します。

gem install smartcard

ICカードリーダーをPCに接続し、

ruby -rsmartcard -e 'pp Smartcard::PCSC::Context.new.readers'

を実行し、ICカードリーダー名が表示されれば接続成功です。

アプリケーション識別子の取得

実際にHPKIテストカードから情報を取得していきます。

ガイドラインの「附属書A(参考)PKI カードアプリケーション利用のシーケンス」にある「A.2.2 JIS X 6320-15 に従った PKI カードアプリケーションの検索と利用」を実装していきます。

f:id:medley_inc:20180712152004p:plain 引用 ガイドライン

prog01.rb

# prog01.rb
require "smartcard"

def puts_response(response)
  puts "status = %04X" % response[:status]
  puts "data = %s" % response[:data].map { |i| "%02X" % i }.join(" ")
end

context = Smartcard::PCSC::Context.new
begin
  card = context.card context.readers.first

  # SELECTコマンドで`E8 28 BD 08 0F`をパーシャル指定したDFを指定
  apdu = [0x00,
          0xA4,
          0x04,
          0x00,
          0x05,
          0xE8, 0x28, 0xBD, 0x08, 0x0F,
          0x00]
  response = card.transmit apdu.pack("C*")
  response = Smartcard::Iso::IsoCardMixin.deserialize_response response.unpack("C*")
  puts_response response

  while response[:status] == 0x9000
    # SELECTコマンドで次のDFを探す
    apdu = [0x00,
            0xA4,
            0x04,
            0x02,
            0x05,
            0xE8, 0x28, 0xBD, 0x08, 0x0F,
            0x00]
    response = card.transmit apdu.pack("C*")
    response = Smartcard::Iso::IsoCardMixin.deserialize_response response.unpack("C*")
    puts_response response
  end
ensure
  context.release
end

上記のプログラムを実行すると、次のような出力が得られます。

status = 9000
data = 6F 12 84 10 E8 28 BD 08 0F A0 00 00 03 91 00 00 00 00 00 01
status = 9000
data = 6F 12 84 10 E8 28 BD 08 0F A0 00 00 03 91 00 00 00 00 00 02
status = 6A82
data =

SELECTコマンドを発行するとBER-TLVで符号化されたFCI(ファイル制御情報)が取得できます。

1つ目のデータから見ていきます。

1バイト目は6Fなので、このデータはファイル制御パラメタ及びファイル管理データの集合を表します。

f:id:medley_inc:20180712152144p:plain 引用 JIS X 6320-4 表8-ファイル制御情報用の産業感共通利用テンプレート

2バイト目は12なので、後続するデータの長さが18バイトあることを表します。

3バイト目は84なので、データ要素がDF名であることを表します。

f:id:medley_inc:20180712152208p:plain 引用 JIS X 6320-4 表10-ファイル制御パラメタデータオブジェクト

4バイト目は10なので、後続するデータの長さが16バイトあることを表します。 5バイト目以降は、DF名(= アプリケーション識別子)です。

2つ目のデータもデータ構造は同じなため省略します。

これでHPKIテストカードには、

  • E8 28 BD 08 0F A0 00 00 03 91 00 00 00 00 00 01
  • E8 28 BD 08 0F A0 00 00 03 91 00 00 00 00 00 02

という2つのアプリケーション識別子が含まれていることが分かります。

公開鍵証明書を取得する

前段にてHPKIテストカードに含まれているアプリケーション識別子が分かりましたので、次は公開鍵証明書を取得していきます。

ガイドラインの「A.3.2 証明書の読み出し」にあるコマンドの通りにAPDUを発行しても、正しいデータは返ってきません。 これは、HPKIテストカードのEF識別子が、ガイドラインに記載されているEF識別子とは異なるためです。

HPKIカードはJIS X 6320に準拠しているため、各種暗号情報オブジェクトへのパス情報を含んだEF.ODが存在しています。 このEF.ODを使い公開鍵証明書へのパスを取得してから、公開鍵証明書を取得していきます。

f:id:medley_inc:20180712152231p:plain f:id:medley_inc:20180712152245p:plain 引用 ガイドライン

EF.ODを読み込む

prog02.rb

# prog02.rb
require "smartcard"
require "openssl"

def puts_response(response)
  puts "status = %04X" % response[:status]
  puts "data = %s" % response[:data].map { |i| "%02X" % i }.join(" ")
end

def decode_asn1(response)
  data = response[:data].reverse_each.drop_while { |i| i == 0xFF }.reverse
  return if data.empty?
  OpenSSL::ASN1.decode_all data.pack("C*")
end

context = Smartcard::PCSC::Context.new
begin
  card = context.card context.readers.first

  [
    [0xE8, 0x28, 0xBD, 0x08, 0x0F, 0xA0, 0x00, 0x00, 0x03, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01],
    [0xE8, 0x28, 0xBD, 0x08, 0x0F, 0xA0, 0x00, 0x00, 0x03, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02]
  ].each do |aid|
    # SELECTコマンドでアプリケーションを選択する
    apdu = [0x00,
            0xA4,
            0x04,
            0x00,
            0x10,
            *aid,
            0x00]
    card.transmit apdu.pack("C*")

    # EF.ODの読み出し
    apdu = [0x00,
            0xB0,
            0x91,
            0x00,
            0x00]
    response = card.transmit apdu.pack("C*")
    response = Smartcard::Iso::IsoCardMixin.deserialize_response response.unpack("C*")
    pp decode_asn1 response
  end
ensure
  context.release
end

EF.ODを読み込むとDER符号化されたデータが返ってきます。 これを OpenSSL::ANS1 モジュールで復号化すると、次に取得するべきEF識別子が分かります。

EF.ODのASN.1定義は以下のようになっているため、タグが4であるデータを読み込めば良さそうです。

CIOChoice ::= CHOICE {
  privateKeys          [0] PrivateKeys,
  publicKeys           [1] PublicKeys,
  trustedPublicKeys    [2] PublicKeys,
  secretKeys           [3] SecretKeys,
  certificates         [4] Certificates,
  trustedCertificates  [5] Certificates,
  usefulCertificates   [6] Certificates,
  dataContainerObjects [7] DataContainerObjects,
  authObjects          [8] AuthObjects,
}

prog02.rbを実行して実際に得られたデータ

[
 # 中略
 #<OpenSSL::ASN1::ASN1Data:0x00007f8b8e0ef7b0
  @indefinite_length=false,
  @tag=4,
  @tag_class=:CONTEXT_SPECIFIC,
  @value=
   [#<OpenSSL::ASN1::Sequence:0x00007f8b8e0ef7d8
     @indefinite_length=false,
     @tag=16,
     @tag_class=:UNIVERSAL,
     @tagging=nil,
     @value=
      [#<OpenSSL::ASN1::OctetString:0x00007f8b8e0ef800
        @indefinite_length=false,
        @tag=4,
        @tag_class=:UNIVERSAL,
        @tagging=nil,
        @value="\x00\x04">]>]>
 # 中略
]
[
 # 中略
 #<OpenSSL::ASN1::ASN1Data:0x00007f8b8d118df0
  @indefinite_length=false,
  @tag=4,
  @tag_class=:CONTEXT_SPECIFIC,
  @value=
   [#<OpenSSL::ASN1::Sequence:0x00007f8b8d118e18
     @indefinite_length=false,
     @tag=16,
     @tag_class=:UNIVERSAL,
     @tagging=nil,
     @value=
      [#<OpenSSL::ASN1::OctetString:0x00007f8b8d118e40
        @indefinite_length=false,
        @tag=4,
        @tag_class=:UNIVERSAL,
        @tagging=nil,
        @value="\x00\x04">]>]>,
 # 中略
]

どちらのアプリケーションも00 04がEF.CD(証明書オブジェクト情報)のEF識別子だということが分かります。

EF.CDを読み込む

prog03.rb

# prog03.rb
require "smartcard"
require "openssl"

def puts_response(response)
  puts "status = %04X" % response[:status]
  puts "data = %s" % response[:data].map { |i| "%02X" % i }.join(" ")
end

def decode_asn1(response)
  data = response[:data].reverse_each.drop_while { |i| i == 0xFF }.reverse
  return if data.empty?
  OpenSSL::ASN1.decode_all data.pack("C*")
end

context = Smartcard::PCSC::Context.new
begin
  card = context.card context.readers.first

  [
    [0xE8, 0x28, 0xBD, 0x08, 0x0F, 0xA0, 0x00, 0x00, 0x03, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01],
    [0xE8, 0x28, 0xBD, 0x08, 0x0F, 0xA0, 0x00, 0x00, 0x03, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02]
  ].each do |aid|
    # SELECTコマンドでアプリケーションをapdu
    選択する = [0x00,
            0xA4,
            0x04,
            0x00,
            0x10,
            *aid,
            0x00]
    card.transmit apdu.pack("C*")

    # SELECTコマンドでEF識別子`00 04`を選択する
    apdu = [0x00,
            0xA4,
            0x02,
            0x0C,
            0x02,
            0x00, 0x04]
    card.transmit apdu.pack("C*")

    # READ BINARYコマンドでファイルを読み込む
    data = []
    offset = 0
    loop do
      apdu = [0x00,
              0xB0,
              (offset & 0x7FFF) >> 8,
              (offset & 0x00FF),
              0x00]
      response = card.transmit apdu.pack("C*")
      response = Smartcard::Iso::IsoCardMixin.deserialize_response response.unpack("C*")
      data.concat response[:data]
      break if response[:data].all? { |e| e == 0xFF }
      break unless response[:status] == 0x9000
      offset += response[:data].size
    end
    pp decode_asn1 data: data
  end
ensure
  context.release
end

prog03.rbを実行して実際に得られたデータ

[
 # 中略
 #<OpenSSL::ASN1::Sequence:0x00007ffdf99aaf70
  @indefinite_length=false,
  @tag=16,
  @tag_class=:UNIVERSAL,
  @tagging=nil,
  @value=
   [#<OpenSSL::ASN1::OctetString:0x00007ffdf99ab038
     @indefinite_length=false,
     @tag=4,
     @tag_class=:UNIVERSAL,
     @tagging=nil,
     @value="\x00\x16">,
    #<OpenSSL::ASN1::Integer:0x00007ffdf99aafe8
     @indefinite_length=false,
     @tag=2,
     @tag_class=:UNIVERSAL,
     @tagging=nil,
     @value=#<OpenSSL::BN 0>>,
    #<OpenSSL::ASN1::ASN1Data:0x00007ffdf99aaf98
     @indefinite_length=false,
     @tag=0,
     @tag_class=:CONTEXT_SPECIFIC,
     @value="\x05\x17">]>
 # 中略
]
[
 # 中略
 #<OpenSSL::ASN1::Sequence:0x00007ffdfa072308
  @indefinite_length=false,
  @tag=16,
  @tag_class=:UNIVERSAL,
  @tagging=nil,
  @value=
   [#<OpenSSL::ASN1::OctetString:0x00007ffdfa072448
     @indefinite_length=false,
     @tag=4,
     @tag_class=:UNIVERSAL,
     @tagging=nil,
     @value="\x00\x16">,
    #<OpenSSL::ASN1::Integer:0x00007ffdfa0723d0
     @indefinite_length=false,
     @tag=2,
     @tag_class=:UNIVERSAL,
     @tagging=nil,
     @value=#<OpenSSL::BN 0>>,
    #<OpenSSL::ASN1::ASN1Data:0x00007ffdfa072380
     @indefinite_length=false,
     @tag=0,
     @tag_class=:CONTEXT_SPECIFIC,
     @value="\x05%">]>
]
 # 中略

これで公開鍵証明書ファイルのEF識別子が00 16であることが判明しました。

公開鍵証明書を読み込む

prog04.rb

# prog04.rb
require "smartcard"
require "openssl"

context = Smartcard::PCSC::Context.new
begin
  card = context.card context.readers.first

  [
    [0xE8, 0x28, 0xBD, 0x08, 0x0F, 0xA0, 0x00, 0x00, 0x03, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01],
    [0xE8, 0x28, 0xBD, 0x08, 0x0F, 0xA0, 0x00, 0x00, 0x03, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02]
  ].each do |aid|
    # SELECTコマンドでアプリケーションを選択する
    apdu = [0x00,
            0xA4,
            0x04,
            0x00,
            0x10,
            *aid,
            0x00]
    card.transmit apdu.pack("C*")

    # SELECTコマンドでEF識別子`00 16`を選択する
    apdu = [0x00,
            0xA4,
            0x02,
            0x0C,
            0x02,
            0x00, 0x16]
    card.transmit apdu.pack("C*")

    # READ BINARYコマンドでファイルを読み込む
    data = []
    offset = 0
    loop do
      apdu = [0x00,
              0xB0,
              (offset & 0x7FFF) >> 8,
              (offset & 0x00FF),
              0x00]
      response = card.transmit apdu.pack("C*")
      response = Smartcard::Iso::IsoCardMixin.deserialize_response response.unpack("C*")
      data.concat response[:data]
      break if response[:data].all? { |e| e == 0xFF }
      break unless response[:status] == 0x9000
      offset += response[:data].size
    end
    cert = OpenSSL::X509::Certificate.new(data.reverse_each.drop_while { |i| i == 0xFF }.reverse.pack("C*"))
    puts cert.to_text
  end
ensure
  context.release
end

HPKIテストカードからDER符号化された公開鍵証明書データが取得できるので、OpenSSL::X509::Certificate.newインスタンス化できます。

上記のprog04.rbを実行すると下記のような出力が得られます。

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 13023 (0x32df)
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=JP, O=Japan Medical Association, OU=Digital Certificate Center, CN=HPKI-01-HPKI_JV2-forNonRepudiation
        Validity
            Not Before: Aug 15 15:00:00 2017 GMT
            Not After : Aug 15 14:59:59 2018 GMT
        Subject: C=JP, CN=JMACombi20413/serialNumber=TESTC20413
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:94:dd:09:40:f4:58:f9:0f:ec:3a:ea:e3:47:33:
 # 中略
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Authority Key Identifier:
                keyid:44:E9:20:05:4D:6D:C4:B7:FA:4B:F0:1B:C6:EA:C8:D6:5B:16:22:F4
                DirName:/C=JP/O=Ministry of Health, Labour and Welfare/OU=Director-General for Policy Planning and Evaluation/OU=MHLW HPKI Root CA V2
                serial:02

            X509v3 Subject Key Identifier:
                9E:E5:71:59:1E:A7:FC:1E:4A:31:F8:7B:30:0B:E3:7F:05:3D:9A:40
            X509v3 Key Usage: critical
                Non Repudiation
            X509v3 CRL Distribution Points:

                Full Name:
                  URI:http://crl.pki.med.or.jp/repository/crl/crl-sign2.crl

            X509v3 Subject Directory Attributes:
                0402..(..B..1(1&0$."1 ...
*.............Medical Doctor
            X509v3 Certificate Policies: critical
                Policy: 1.2.392.100495.1.5.1.1.0.1
                  CPS: http://www.pki.med.or.jp/certpolicy/

    Signature Algorithm: sha256WithRSAEncryption
         84:ae:95:45:5e:e7:64:8b:0c:6e:20:5f:9f:1f:0d:5c:ae:4a:
 # 中略

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 12927 (0x327f)
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=JP, O=Japan Medical Association, OU=Digital Certificate Center, CN=HPKI-01-HPKI_JV2-forAuthentication-forIndividual
        Validity
            Not Before: Aug 15 15:00:00 2017 GMT
            Not After : Aug 15 14:59:59 2018 GMT
        Subject: C=JP, CN=JMACombi20413/serialNumber=TESTC20413
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:c6:f9:06:26:58:5e:11:b7:12:f2:8a:3e:97:0a:
 # 中略
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Authority Key Identifier:
                keyid:62:12:93:82:DE:3C:D7:FF:A8:D3:63:01:D3:01:6A:AE:6C:3B:C0:D4
                DirName:/C=JP/O=Ministry of Health, Labour and Welfare/OU=Director-General for Policy Planning and Evaluation/OU=MHLW HPKI Root CA V2
                serial:03

            X509v3 Subject Key Identifier:
                45:2B:7B:B4:47:89:3D:6C:05:6D:82:4D:4C:C8:80:B8:B4:B0:89:81
            X509v3 Key Usage: critical
                Digital Signature
            X509v3 CRL Distribution Points:

                Full Name:
                  URI:http://crl.pki.med.or.jp/repository/crl/crl-auth2.crl

            X509v3 Subject Directory Attributes:
                0402..(..B..1(1&0$."1 ...
*.............Medical Doctor
            X509v3 Certificate Policies: critical
                Policy: 1.2.392.100495.1.5.1.2.0.1
                  CPS: http://www.pki.med.or.jp/certpolicy/

    Signature Algorithm: sha256WithRSAEncryption
 # 中略

それぞれのアプリケーションから正しく公開鍵証明書が取得できました。

電子認証ガイドラインによると、電子認証に使用する証明書はIssuerのCN(Common Name)がHPKI-01-*-forAuthentication-forIndividualであることが定められているため、 使用したHPKIテストカードでは、電子認証に使用するアプリケーション識別子はE8 28 BD 08 0F A0 00 00 03 91 00 00 00 00 00 02であることが分かります。 また、電子署名に使用するアプリケーション識別子はE8 28 BD 08 0F A0 00 00 03 91 00 00 00 00 00 01であることが分かりました。

最後に

以上でガイドラインの「A.2.2 JIS X 6320-15 に従った PKI カードアプリケーションの検索と利用」にある「PKI カードアプリケーションの検索」まで実装できました。

今後、次のステップである暗号計算を実装していきたいと思います。

電子レセプトについて調べた話

こんにちは、開発本部の竹内です。最近子どものプリンセスへの強い憧れに若干引いております。

さて先日、TechLunchという社内勉強会で「電子レセプト」について話しましたので、こちらでも簡単に紹介させていただきます。

レセプトとは

ところで、みなさまは「レセプト」についてご存知でしょうか?私はメドレーに入社するまで知りませんでした。

レセプトとは医療機関が支払基金へ診療報酬を請求するための明細書情報のことです。

と言っても、初めて聞かれる方もいらっしゃると思いますので、医療機関におけるお金の流れとともに簡単に説明します。

http://www.ssk.or.jp/kikin.images/kikin_image01.png

(支払基金ってどんなところ?|社会保険診療報酬支払基金より)

医療機関は「診療」の対価として、被保険者等(≒患者)からお金を受け取るわけですが、被保険者の加入する保険や公費によってその額は変わります。負担割合が3割の場合、残りの7割を被保険者が加入する保険組合などへ請求する必要があります。

この保険組合などへの請求を取りまとめ、内容を審査しているのが支払基金と呼ばれる組織で、医療機関は月に一度、前月の患者ごとの診療点数を計算し「レセプト」として支払基金に提出することになります。

http://www.ssk.or.jp/kikin.images/kikin_image03.png

(支払基金ってどんなところ?|社会保険診療報酬支払基金より)

レセプトには、請求する診療点数のほか、医療機関の情報、被保険者の情報(氏名などの基本情報、加入している保険者情報)、診療行為や傷病名に関する情報などが含まれています。

「レセプト」には紙と電子データとありますが、現在は原則として電子レセプトを提出することが求められているそうです(電子レセプト請求に係る猶予措置及び免除措置について|社会保険診療報酬支払基金)。

電子レセプトとレセ電ビューア

電子レセプトについての仕様は支払基金によって公開されています。今回は「電子レセプト作成の手引き」という資料を元に「医科」のレセプトについて調べて発表しました。

発表資料はこちら。 speakerdeck.com

電子レセプトの実体はCSV形式のシンプルなテキストファイルです(拡張子はUKEなので、UKEファイルと呼ぶこともあるようです)。ただ、電子レセプトの仕様を把握したとしても、やはりCSVファイルを見て内容を把握するのは至難の業です。ファイル上では診療行為や医薬品、傷病名はコードとして表現されているため、マスタデータを参照しなければその内容まで理解することはできないからです。

そこで登場するのが、レセ電ビューアというツールで、ORCA Projectによって公開されているフリーの電子レセプトビューアです。「レセ電=電子レセプト」ですね。

www.orca.med.or.jp

(※上記ページでは「レセ電ビューア」と「レセ電ビューワ」が混在していますが、本ブログでは「レセ電ビューア」で統一しています)

レセ電ビューアはWindowsUbuntuで動作し、上述したUKEファイルを読み込み、見やすく表示してくれる便利ツールです。ここからはレセ電ビューアをインストールし、電子レセプトを読み込んで表示するところまでを紹介したいと思います。

レセ電ビューアのインストール

レセ電ビューアはWindowsUbuntu上で動作しますので、まずはUbuntu環境を準備します。私はVirtualBox上にUbuntu環境を用意しました。 公式のインストールマニュアル(ubuntu環境へのレセ電ビューアインストール)に基づいて作業していきます。

# Keyringとapt-lineの追加
$ sudo su
$ wget -q https://ftp.orca.med.or.jp/pub/ubuntu/archive.key
$ apt-key add archive.key
$ wget -q -O \
/etc/apt/sources.list.d/jma-receipt-xenial50.list \
https://ftp.orca.med.or.jp/pub/ubuntu/jma-receipt-xenial50.list
$ apt-get update
$ apt-get dist-upgrade

# レセ電ビューアパッケージインストール
$ apt-get install jma-receview
$ apt-get install jma-receview-server

# レセ電ビューア起動
$ jma-receview

レセ電ビューアの設定

電子レセプトに含まれる診療行為などのコードに対応するマスタデータを参照するため、日レセ(jma-receipt)のDBを利用することができます。今回はDBFile形式でDBに接続します。レセ電ビューアに付属するスクリプトを実行し、jma-receiptのDBから必要なテーブルをダンプすることができます。このファイルをレセ電ビューアに設定することで、電子レセプトの表示がよりわかりやすくなります。

# レセ電ビューアで使うDBFileを作る
# https://ftp.orca.med.or.jp/pub/receview/manual/jma-receview.pdf
# 「2.7.4 DBFile の作成方法」
$ sudo su orca
$ ls -la /usr/share/jma-receview/db/
$ mkdir /var/tmp/dbfile
$ cp /usr/share/jma-receview/db/make_dbfile.sh /var/tmp/dbfile/
$ cd /var/tmp/dbfile/
$ sh ./make_dbfile.sh 20170101
$ ls -lh
合計 6.9M
-rwxr-xr-x 1 orca orca 2.9K  518 14:55 make_dbfile.sh
-rw-r--r-- 1 orca orca 1.2M  518 14:57 tbl_byomei.rdb
-rw-r--r-- 1 orca orca   89  518 14:57 tbl_dbkanri.rdb
-rw-r--r-- 1 orca orca 330K  518 14:57 tbl_hknjainf.rdb
-rw-r--r-- 1 orca orca 9.6K  518 14:57 tbl_labor_sio.rdb
-rw-r--r-- 1 orca orca 343K  518 14:57 tbl_syskanri.rdb
-rw-r--r-- 1 orca orca 5.1M  518 14:57 tbl_tensu.rdb

f:id:medley_inc:20180706112205p:plain

接続設定で「DBFile」を選択し、先ほど作成したDBFileを選択します。

レセ電ビューア近影

f:id:medley_inc:20180706112221p:plain

「ファイル」からUKEファイルを開くと、レセプトに基づいて患者基本情報、保険・公費情報、診療行為情報のほか、紙レセプトのプレビューや患者単位での電子レセプトを表示することができます。また、「編集モード」に切り替えることで患者情報や病名の編集が可能で、編集した内容でレセプトを再出力することもできるようです。

おまけ

ここまで紹介してきたレセ電ビューアですが、調べてみるとどうやらRubyで実装されているようです。これらのコードを読んでいくことで新たな地平を開くことができるかもしれません。

$ dpkg -L jma-receview | grep ruby
/usr/lib/ruby
/usr/lib/ruby/2.3.0
/usr/lib/ruby/2.3.0/jma
/usr/lib/ruby/2.3.0/jma/receview
/usr/lib/ruby/2.3.0/jma/receview/menu.rb
/usr/lib/ruby/2.3.0/jma/receview/intconv.rb
/usr/lib/ruby/2.3.0/jma/receview/base.rb
/usr/lib/ruby/2.3.0/jma/receview/dbfile_lib.rb
/usr/lib/ruby/2.3.0/jma/receview/gui.rb
/usr/lib/ruby/2.3.0/jma/receview/yearconv.rb
/usr/lib/ruby/2.3.0/jma/receview/exception.rb
/usr/lib/ruby/2.3.0/jma/receview/api.rb
/usr/lib/ruby/2.3.0/jma/receview/config.rb
/usr/lib/ruby/2.3.0/jma/receview/image.rb
/usr/lib/ruby/2.3.0/jma/receview/dialog.rb
/usr/lib/ruby/2.3.0/jma/receview/upstart.rb
/usr/lib/ruby/2.3.0/jma/receview/dbslib.rb
/usr/lib/ruby/2.3.0/jma/receview/thread.rb
/usr/lib/ruby/2.3.0/jma/receview/receview.rb
/usr/lib/ruby/2.3.0/jma/receview/sickname_edit.rb
/usr/lib/ruby/2.3.0/jma/receview/dayconv.rb
/usr/lib/ruby/2.3.0/jma/receview/isoimage.rb
/usr/lib/ruby/2.3.0/jma/receview/help.rb
/usr/lib/ruby/2.3.0/jma/receview/other_csv.rb
/usr/lib/ruby/2.3.0/jma/receview/print.rb
/usr/lib/ruby/2.3.0/jma/receview/env.rb
/usr/lib/ruby/2.3.0/jma/receview/generation.rb
/usr/lib/ruby/2.3.0/jma/receview/red2cairo.rb
/usr/lib/ruby/2.3.0/jma/receview/strconv.rb
/usr/lib/ruby/2.3.0/jma/receview/log.rb
/usr/lib/ruby/2.3.0/jma/receview/hokenconv.rb
/usr/lib/ruby/2.3.0/jma/receview/keybind.rb
/usr/lib/ruby/2.3.0/jma/receview/version.rb
/usr/lib/ruby/2.3.0/jma/receview/gtk2_fix.rb
/usr/lib/ruby/2.3.0/jma/receview/preview_widget.rb
/usr/lib/ruby/2.3.0/jma/receview/command.rb

# /usr/bin/jma-receviewもrubyで書かれてました(12000行以上ある…)

まとめ

今回は電子レセプトとレセ電ビューアについて、簡単に紹介しました。

わたしたちの生活とは切り離せない「医療」に関するシステムや仕様は、意外と一般公開されているものもあり、誰でも触れることができます。ただ、動作環境が制限されていたり、インターネット界隈のエンジニアがよく目にする技術とは異なるスタックで構築されていたり、それなりにハードルがあるように感じています。

これらのハードルを下げ、より多くの人が「調べてみよう」「ちょっと触ってみよう」と思うようになれば、医療に関わるシステムや仕様もよりシンプルで使いやすいものになり、ひいては各医療問題の解決・患者体験の改善につながっていくのではないかな、メドレーがつなげていきたいなと思っています。

最後はいいことを言って締めたい性分なのですが、いかがだったでしょうか。

ここまでお読みいただき、ありがとうございました。

お知らせ

メドレーの開発にご興味ある方は、こちらからご連絡ください。 www.medley.jp

7/27に開催されるデブサミ2018Summerに協賛させていただきます。ぜひ遊びにいらしてください。 event.shoeisha.jp

社内勉強会TechLunchで"JavaScript ASTことはじめ"という発表をしました

みなさん、こんにちは。開発本部エンジニアの平木です。こちらのブログの投稿自体はほぼ1年ぶりになりそうな勢いですが、みなさまお元気でしょうか?

弊社で定期的に開催してる社内勉強会TechLunchで自分の順番が回ってきたため、どうしようか迷った末にJavaScript ASTことはじめという発表をしたので、そのことについて書いていきます。

なぜJavaScript ASTについて話そうと思ったのか

現在、弊社のエンジニアメンバーのバックグラウンドで一番多数派なのは「元サーバサイドエンジニア」です。もちろん、業務ではサーバサイド・フロントエンド・ネイティブアプリとバックグラウンドに関わらず、必要に応じて分け隔てなく開発しています。

とはいえ、ちゃんとサービス開発自体はできるとしても、やはり得意な分野以外で基本原理など含めて把握して開発できるかというと、ちょっと難しいところもあります。しかし、そういった基本原理なんかを知っていると、その言語やツールなどの理解が捗るのは確かですよね。

そんな中、弊社で開発している人間がほぼ全て恩恵を受けているはずなのに、具体的にどんな風に動いているのかが一番分かりにくいであろうBabelひいてはJavaScript ASTの話をしたら、まあ興味持って話を聞いてくれるかなーということででこのテーマを選んだ次第です。

どのように伝えるか

自分はJavaScript ASTについてとても詳しいわけではないのですが、以前仕事でacornを使ってコンバータみたいなのを作ったりしていたので、それなりに興味は持っているという人間です。

ですので、どうやって紹介をしようかと悩んだ結果、ほぼ全面的にAST Exploreに頼っていくというスタイルにしました。AST Exploreは本当に最高ですね。前述の仕事をしていたときはこんな便利なツールはなかったんで、ひたすらASTに変換するコード書いては出来たASTを見て、それをトランスフォームさせて結果と睨めっこして試行錯誤するという毎日でした。

ということで、当日のスライドはこちらになります。

speakerdeck.com

スライドで紹介したデモはそれぞれこちらになります。

今回伝えたかったこと

まず、ASTがJavaScriptの発展にとても寄与しているものだということを知ってもらいたかったため、JavaScript ASTの今までの簡単な流れや、現在どのような形で使われているのかの説明をしました。(個人的にNode.jsの誕生とJavaScript ASTの存在が現在のフロントエンドの発展にとても重要だと思っているので)

最初のうちは聞いてる人も「何の話なんだろ…」感がありましたが、やはり実際に自分が使っているツールなどに使われているという説明をしたあとだと、聞いているメンバーも俄然興味が出てきたという雰囲気になった気がします(当社比)。

ASTの文法などは自分が説明するよりは、ちゃんと資料が揃っているので必要な部分以外簡略化しました。逆にちょっと端折りすぎたきらいもありますが、興味を持ったときに何となくでも調べる道標くらいにはなるかなと考えています。

次に知ってもらいたかったのは、やろうと思えばBabelのプラグインなんかもASTで作れちゃいますよということでした。仮にいきなり「Babelプラグイン作りましょう」となったとしても正直あまりピンと来ないと思いますが、どういう原理でプロダクトが動いているのか?が分かると、babel-handbookなどを読んでも理解が進むのではないかと思います。

AST Exploreのこと

このように今回の発表で全面的に活躍したAST Exploreですが、TechLunch中でも軽い説明だけで使ってしまったので、使いかたなど簡単にご紹介していきます。

AST Exploreとは

AST ExploreFelix Klingさんが、2014年頃から開発しているプロダクトです。

余談ですが、Felixさんは現在Facebookで働いていらっしゃるようで、facebook/jscodeshiftreactjs/react-docgenなんかの開発にも携わっていらっしゃる模様。(react-docgenはbabylonを使っているようですが)

ここまで書いてきた通りに、このツールは色々な言語をコピペするだけでASTをツリー形式で分かりやすく表示したり、トランスフォームさせることができたりするというASTを触るには大変便利なツールです。去年のv2.0のアップデートにより、セーブするとgistを匿名で作ってくれてリンクが生成されるなどの便利機能が付きました。

プロジェクトのREADMEに書いていますが、パーサだけであれば、かなりパースできるものが多く、またJavaScript / CSS / 正規表現 / Handlebarsに関してはトランスフォームまでできるようになっています。

READMEから抜粋すると以下のような感じです。

AST Exploreでパースできるもの

  • CSS:
    • cssom
    • csstree
    • postcss + postcss-safe-parser & postcss-scss
    • rework
  • GraphQL
  • Graphviz:
    • redot
  • Handlebars:
    • glimmer
    • handlebars
  • HTML:
    • htmlparser2
    • parse5
  • ICU
  • JavaScript:
    • acorn + acorn-jsx
    • babel-eslint
    • babylon
    • espree
    • esformatter
    • esprima
    • flow-parser
    • recast
    • shift
    • traceur
    • typescript
    • typescript-eslint-parser
    • uglify-js
  • JSON
  • Lua:
    • luaparse
  • Markdown:
    • remark
  • PHP
  • Regular Expressions:
  • Scala
    • Scalameta
  • SQL:
  • WebIDL
  • YAML

実験的だったりするけどパースできるもの

  • ES6: arrow functions, destructuring, classes, …
  • ES7 proposals: async/await, object rest / spread, …
  • JSX
  • Typed JavaScript Flow and TypeScript
  • SASS

パースしたものをトランスフォームできるもの

  • JavaScript
    • babel (v5, v6)
    • ESLint (v1, v2, v3)
    • jscodeshift
    • tslint
  • CSS
    • postcss
  • Regular Expressions
  • Handlebars
    • glimmer

AST Exploreの使い方の簡単な解説

サイトにアクセスするとこのような画面になっているはずです。

f:id:medley_inc:20180622111218p:plain

メイン画面

JavaScriptにフォーカスして解説していきますと、左ペインがASTに変換したいソースコード、右ペインが変換後のASTをツリー構造で見せています。

初期表示時に、左ペインのソースコードをクリックすると該当箇所のASTツリーが展開してハイライトします。また右ペインをポイントするとソースコードの該当箇所がハイライトします。お互いの関係が分かりやすい仕様になっています。

本来JavaScript ASTで生成されるものはJSONオブジェクトになりますが、右ペインの上のTreeJSONのタブを切りかえることによってASTの表示を変更することができます。

ヘッダー部分

ヘッダーに色々な機能がまとまっています。

初期表示では以下のようになっているはずです。

  • Snippet
    • 俗にいうファイルメニュー。
      • 新規作成・(gistへの)セーブ・(gistの)フォーク・シェアがある
  • JavaScript
    • パースする言語選択
      • ここでASTにしたい言語を切り替える
      • 選んだ言語によってTransformが使えなくなる
  • acorn
    • パーサ選択
      • 各言語のパーサを切り替える
  • Transform
    • トランスフォーマ選択
      • 選択した言語にトランスフォーマがあれば選択できるようになる
      • こちらを選択すると2ペインだったのが4ペインになる(後述)
  • default
  • ?
    • ヘルプ
      • GitHubのREADMEに飛ばされるだけです…

JavaScriptのトランスフォーム

先程説明したトランスフォームを選ぶと、メインの画面が4画面になります。

f:id:medley_inc:20180622111427p:plain

今までのソースコードASTツリーは変わりませんが、下に2つペインが追加されます。 左下がトランスフォーマコード、右下がトランスフォームした後のソースコードとなっています。

左下のトランスフォーマを色々触っていくと左上のソースコードが変換されて、右下に表示されるという流れですね。

以下JavaScriptコードのトランスフォームする際のTipsです

  • jscodeshiftを選択するとCtrl + Spaceでjscodeshiftの補完が効くようになります
  • babel-plugin-macroを選ぶとトランスフォーマのコード自体がそのままbabel-pluginとして使えるようになるので、プラグイン作るときに捗るはずです

まとめ

後で参加メンバーに聞いてみましたが、伝えたかったことは、ちゃんと伝わっていた様子だったので安心しました。最後のVue.jsのv1からv2のマイグレーションのデモは紹介した結果、JavaScript AST便利そうという感触になったと思います。

現在弊社のプロダクトで、JavaScript ASTをガッツリと使うようなプロジェクトはないのですが、Babelなどは全プロダクトで使用しており、結構プラグインを多用しているところもあるので、いざというときの基礎知識として覚えておいて損はないはずです。

こういった部分の勉強も欠かさず続けていきたいと改めて思う機会にもなりました。

弊社の開発文化など気になる方は、こちらからどうぞ。 www.medley.jp

Lightning Talks SponsorとしてRubyKaigi 2018に参加してきました

こんにちは!開発本部のエンジニア・後藤です。

メドレーは5/31〜6/2に開催されたRurbyKaigi 2018にLightning Talks Sponsorとして協賛させていただきました(昨年Ruby Sponsorに続き、2年目の協賛です)。

イベント当日は、弊社からCTOの平山、採用・広報の阿部と深澤、エンジニアの田中、宍戸、後藤の6人が参加しました。今回はその様子などをレポートします。

会場の様子

RubyKaigi 2018は仙台国際センターでの開催でした。昨年は広島、一昨年は京都ということで、これで天橋立・宮島・松島の日本三景をめぐる旅が一旦完結になりますね。

f:id:medley_inc:20180605172640j:plain

仙台国際センターは仙台駅から地下鉄東西線で3駅目というアクセスの良い好立地にありながら緑に囲まれた心地よい場所にありました。

メイン会場は1000人収容できる広い会場になっています。各セッション、この会場が埋まるぐらいの盛況ぶりでした。

f:id:medley_inc:20180605172745j:plain

世界地図&日本地図。様々な地域からの参加者がいますね!(rubyistsにmapメソッドをかましてますね。ブロック内容は各参加者がシールを貼って実装。)

f:id:medley_inc:20180605172857j:plain

地図の隣のスポンサーボード覧にメドレーロゴがあることを確認してパシャり。

f:id:medley_inc:20180605173736p:plain

ブースの様子

続いてブースの様子を紹介します。メドレーコーポレートカラーの赤をベースとした以下な感じの仕上がりになりました。

 f:id:medley_inc:20180605173908j:plain フォトスポンサーのラブグラフさんがブースの写真を撮ってくれました!

ノベルティも用意してブースにお越しいただいた方にお配りしていました。ステッカー、うちわ、パンフレットに加えて医療らしさが伝わる絆創膏も用意しました。

f:id:medley_inc:20180605174458j:plain

ブースにはおかげさまで、たくさんの方にお越しいただきました!

f:id:medley_inc:20180605174539j:plain

CTO平山の発表

そして、初日のLightning Talks前のスポンサーのPR枠にて弊社のCTO平山が発表をしました。

f:id:medley_inc:20180606145341p:plain

f:id:medley_inc:20180605175431j:plain

発表では、「医療ヘルスケア分野の課題を解決する」というミッションのもとメドレーが4つの事業を行っていること、また、以前本ブログで紹介しました3本のニュースリリースを含めたこの1年のアップデート内容を中心に紹介させていただきました。

f:id:medley_inc:20180605175456j:plain

公演の途中でのCTO平山からメドレーのことを知っている人?との問いかけで、7・8割の方が挙手をしていたのは感慨深かったです。

当日のスライドはこちらです。 speakerdeck.com

現地での反応

ブース展示、PR枠での発表を通じて以下のような嬉しい反応もいただきました!

ブースでも、2日目以降「1日目のセッション見ましたよー」と話してくださる方も多く、メドレーとそのプロダクトについてRubyistの皆様に知っていただくとても良い機会になっていたと思いました。

セッションの様子

エンジニアはブースでの会社紹介の合間に各自気になったセッションを聴講したりもしました。 感覚ですがmrubyや型、パフォーマンス周りの話題が多かったように思います。まさに2018年現在のRubyを取り巻く環境を表している感じがします。エンジニアとしてこういった技術のセッションを聞けるのは純粋に楽しいですし、日々の開発に活かせそうなネタもあったりと、とても有意義な時間を過ごせました。

f:id:medley_inc:20180605175826j:plain 初日のkeynoteでの1コマ。

Matzさんのkeynoteにもありましたが何事も「塞翁が馬」です。毎年Rubyは死に、そして生まれ変わります(クリスマスに)。Rubyも常に進化していることを肌で感じることのできるセッション群でした。

番外編

さて、メドレー恒例(?)のお参りですが今回は大崎八幡宮に参詣することになりました。

大崎八幡宮の社殿は国宝にも指定されており、安土桃山時代の豪華絢爛な様式の建築でとても雰囲気のある神社でした。

f:id:medley_inc:20180605175920j:plain 参拝する3人。

f:id:medley_inc:20180605175948j:plain 参拝する2人とそれを撮影する2人。

さいごに

昨年に引き続きメドレーのRubyKaigi協賛は2度目になりました。

ブースで会社やプロダクトの説明していると「メドレー知ってます」との声を聞く機会も多く、とても嬉しい限りでした。これまで以上にRuby、医療×ITを盛り上げていければという気持ちを胸に仙台を後にしました。

 f:id:medley_inc:20180605180049j:plain (新幹線から仙台の夕焼けをパシャリ)

お知らせ

弊社では「医療 x ITへの挑戦」に取り組みたいエンジニアのみなさんを心からお待ちしております! 興味がある方は、こちらの「話を聞いてみたい」からご連絡ください。 www.wantedly.com

開発本部の雰囲気をもっと知りたい方は、こちらからどうぞ。 www.medley.jp

アプリエンジニアがのぞいたReact Native 〜メドレーTechLunch〜

こんにちは、開発本部の高井です。メドレー開発本部で行われている勉強会「TechLunch」でReact Nativeについて発表しました。

私は普段はSwift、Kotlin/Javaを使ってネイティブアプリを開発しており、React Nativeに触るのは初めてでした。そこで今回は、アプリエンジニアの視点から、実装するための基本的な知識と弊社の実際の開発で使えそうかを検討した結果についてご紹介します。  

なぜReact Nativeを触ってみようと思ったか

オンライン診療アプリ「CLINICS」の開発では、iOS/Androidアプリをそれぞれのネイティブ言語で別々に開発しているため、実装やレビューの際にはプラットフォーム間の仕様の違いを理解する必要があり、なかなか大変だと感じていました。

これらの課題に対してこちらのブログでも紹介したような施策を行って改善を行ってきましたが、ソースを共通化することでより開発効率を向上できないかと思い、クロスプラットフォーム開発についても調べてみることにしました。

その中でも以下の理由から、今回はReact Nativeについて調べてみることにしました。

  • JavaScript(以下JS)・Reactのため、Webエンジニアがネイティブアプリ開発を行う際のハードルを低くすることができそう
  • UIの実装にネイティブUIを使用しているので自然なデザインやインタラクションを作りやすそう
  • ある程度リリースから時間が経って情報が豊富にある

特に弊社では、ネイティブアプリよりWeb開発をメインに行ってきたエンジニアの方が多く、またWebフロントにReactが使われているプロダクトもいくつかあるので、React Nativeを採用することでチームの開発効率の向上だけでなく、開発本部全体でもネイティブアプリ開発の学習コストが低くなるのではないかと考えました。

そこで、以降ではネイティブアプリエンジニアとWebエンジニアそれぞれにとって、開発しやすいかどうかという観点でReact Nativeの開発方法を見ていきたいと思います。

初期設定について

インストール〜アプリ実行

インストールは公式のGetting Startedにもありますが、以下のコマンドで完了です。  

$ brew install node
$ brew install watchman
$ npm install -g react-native-cli

今回、私が触るにあたってはXcodeAndroid Studioのシミュレータ(エミュレータ)で実行しながら開発しましたが、React NativeはXcodeAndroid Studioがインストールされていなくても、Expoというクライアントアプリを実機にインストールすることで画面をプレビューしながら開発することができます。

これなら、Xcodeのダウンロードを待つ必要もありません!(iOSエンジニアでもアップグレードのたびにXcodeのダウンロードを待つのはイライラします)

次に、プロジェクト作成、シミュレータでのアプリ実行は以下のコマンドで実行できます。

ただし、シミュレータ実行前にXcode Command Line ToolsのインストールとAndroid Studioでいくつかの設定(SDKのインストール、AVDの作成、環境変数の設定)が必要です。  

# プロジェクト作成
$ react-native init AwesomeProject
 # アプリ実行(シミュレータ)
$ cd AwesomeProject
$ react-native run-ios or react-native run-android # Androidの場合、emulatorを別途起動してからでないと実行できない

実装してみた感想

UIの実装方法

React NativeではReact同様UIの各パーツをコンポーネントと呼び、それらを配置することでUIを実装していきます。違いはWebのHTMLの代わりにNativeのUIを描画するためのコンポーネントとして使う点です。

見た目やレイアウトはCSSと似たような形式で記述します。React Nativeで使えるスタイルのプロパティは各コンポーネントで異なりますが、例えば、Viewコンポーネントに設定できるプロパティには以下のようなものがあります。

Webで使われているものと全く同じというわけではないですが、flex、margin、borderなどの使い慣れているCSSのプロパティ名で設定できるので、Webエンジニアにとっては実装のハードルが下がるのではないかと思いました。ただ、普段CSSを触っていないネイティブアプリエンジニアにとっては学習コストがかかると思います。

また一度ビルドすれば、JSによる修正内容をビルドなしでシミュレータに反映することができるので、View周りの調整は効率的にできそうでした。

 

// js/components/home.js

 const renderItem = ({ item, index }) => (

   <View style={styles.row}>

     <Text style={styles.title}>

       {parseInt(index, 10) + 1}

       {". "}

       {item.title}

     </Text>

     <Text style={styles.description}>{item.description}</Text>

   </View>

 );

const styles = StyleSheet.create({

 row: {

   borderBottomWidth: 1,

   borderColor: "#ccc",

   padding: 10,

 },

 title: {

   fontSize: 15,

   fontWeight: "600",

 },

 description: {

   marginTop: 5,

   fontSize: 14,

 },

});

ただ、OSごとにデザインを合わせる方針にしない限りは、コードも別々になるところがわりと多くなりそうだと感じました。

例えば、TabBar(iOS)とDrawerLayout(Android)、DatePicker(iOS)とTimePicker(Android)、ProgressView(iOS)とProgressBar(Android)などはReact Nativeでは別コンポーネントとして提供されていて、APIも違っていました。

画面遷移

画面遷移(プッシュ、モーダル、タブ遷移など)のためにAPIとして公式で提供されているのはiOSのみでAndroidは別途、実装するか、サードパーティのライブラリを使用する必要があります。

わたしが使ってみたreact-native-navigationはモーダル、プッシュなどの遷移がネイティブAPIベースで実装されているので、ネイティブ言語で実装した場合と比べて違和感なく実装することができました。

下記のような形でプッシュやモーダル表示での遷移ができます。特定の画面に戻る機能については開発中であったり、機能的な制約は少しありそうです。そういう細かいところはネイティブ言語でやった方が自由が効くので、良いなと思います。

それでも、iOSAndroidでは複数画面の管理や遷移についての考え方が違い、Androidを初めて開発した時に同じことを実現するのが難しかった覚えがあるので、共通の方法で実現できるのは便利でした。特にWebだとあまり画面間の遷移について考えることはないと思うので、共通化されているとネイティブアプリ開発の学習コストが下がると思います。  

this.props.navigator.push({ // プッシュ

 screen: 'example.PushedScreen',

 title: 'Pushed Screen'

});
 
this.props.navigator.pop({ // 前の画面に戻る

 animated: true,

 animationType: 'fade',

});

 

this.props.navigator.showModal({ // モーダル

 screen: "example.ModalScreen",

 title: "Modal",

 animationType: 'slide-up'

});

ネットワーク周り

ネットワーク経由でデータを取得して、モデルに変換し、アプリ内で使うというよくある操作を行うにはFetch APIを利用します。Fetch APIはJSで提供されているPromiseベースのAPIです。例えば、以下のような形でJSONを返すAPIからデータを取得し、receiveHelthNews関数にオブジェクトに変換した配列を渡すことができます。JSのAPIなのでWebエンジニアにとっては使いやすいのではないかと思います。

ネイティブ言語でそれぞれ実装する場合は、URLSession(iOS)とHttpURLConnection(Android)、あるいは各プラットフォーム向けに提供されているサードパーティのライブラリなどを使うと思いますが、当然、APIは異なるのでそれぞれの実装方法を把握しないといけなくなります。それに比べると学習コストは低くなりそうです。

// js/actions/index.js

export function fetchHelthNews() {

 return dispatch =>

   fetch(constructHealthNewsUrl())

     .then(response => response.json())

     .then(json => dispatch(receiveHelthNews(json.articles)))

     .catch((error) => {

       console.log(error);

     });

}

ネイティブアプリ特有の機能について

その他のアプリ開発でよく使うネイティブアプリ特有の機能を実装する方法は以下のようになります。多くの機能がサードパーティのライブラリに依存しているので、各言語のバージョンアップ時の対応が少し心配ではありますが、よく使う機能については実現することができます。

  • Push通知:AndroidはハンドリングするAPIが公式で提供されていないのでサードパーティのライブラリを使う
  • カメラ、キーチェーンアクセス/ユーザデフォルト:公式のAPIは提供されていないのでサードパーティのライブラリを使う
  • 位置情報の取得:公式APIが提供されている
  • ディープリンク:アプリ起動時にハンドリングを行うAPIは提供されている。ユニバーサルリンクやIntentFilterの設定は各プラットフォームで個別に必要になる

リリースはどうやるか

アーカイブやストア配布についてはネイティブアプリの配布と同じプロセスになります。

各プラットフォームで証明書等の設定を行い、XcodeAndroid Studio、あるいはCLIでコマンドを実行して、ipa / apkファイルを作成し、各Storeにアップロードする必要があります。  

CIはBitriseなどが使えます。Bitriseでビルドを試してみましたが、React Nativeのリポジトリと接続したときにできるデフォルトのワークフローを使えば、同時に2プラットフォームのアーカイブが作成できて便利でした。  

あと、まだ試せてはいないのですがCodePushを使えば、審査に提出することなしに既存のアプリを変更することもできるらしいので、非常に便利だと思いました。    

どんな場合にReact Nativeを採用できそうか?

React Nativeで開発することで、Web開発者の視点で見るとプラットフォームのネイティブ言語で開発するよりも、だいぶ学習コストが下がるのではないかと思いました。またサードパーティのライブラリを使えば、機能的に大きな問題となるようなことはなさそうでした。ただ、画面遷移のライブラリがそうだったように、もしやりたいことができないという場合は、妥協しないといけない部分が出てきそうだと思いました。

一方、アプリエンジニアにとっては、慣れるまではかなり開発速度が下がりそうなのでデメリットも大きいかなと思いました。ReactとJS、またReduxなども新たに理解しつつ開発していたので結構ハードルが高いと感じました。開発環境もビルドが通ってるうちは、View周りの調整がすぐに確認できて良かったのですが、ランタイムエラーになるなどで、シミュレータがリロードできなくなった場合に再度ビルドし直すということがよく起こり、常に快適に開発できるというわけではありませんでした。

運用面でみると一通りアプリ開発に必要そうなツールは揃っているし(クラッシュ監視、CI、テスト配信、リリース)、Code Pushなど便利なツールもあるので利点が多いと思いました。

結論としては、Webエンジニアが社内に多かったり、開発チームにReact、JSが得意なメンバーがいるなら、実際の開発でも使えるかなと思いました。ただ、アプリエンジニアにとってはかなりストレスがたまるプロジェクトになりそうだと感じました。

まとめ

自分の現状で考えると、アプリは得意だけどJSやReactにそこまで詳しくないので、もし直近のプロジェクトで難易度もそこそこ高いようであれば、正直あんまり使いたくないなあという気持ちがあります。ただ、技術の幅を広げるとか、組織全体のポータビリティなどの観点で考えると利点が結構あるのかなと思います。チャンスがあればぜひ、チャレンジしてみたいです。

わたしはどちらかというと技術や開発ツールの新しさよりも、ユーザとの接点のところで新しいことや面白いことを追求したいと思っていますが、開発効率や品質の向上のために最適なものを選択できるように、今後も新しいツールのキャッチアップは積極的に行っていきたいと思っています。

お知らせ

メドレーは、5/31-6/2に開催されるRubyKaigi 2018にLTスポンサーとして協賛します。ブースも構えておりますので、イベントにお越しになる方は、ぜひブースにも遊びにいらしてください!

rubykaigi.org

ElastiCache for Redis 運用小話 〜メドレー・TechLunch〜

こんにちは、開発本部の後藤です。医療介護の求人サイト「ジョブメドレー」の開発を担当しています。

ジョブメドレーでは各種キャッシュや sidekiq のqueue等にElastiCache for Redisを利用しています。

先日、メドレーで定期開催している社内勉強会TechLunchにて、ジョブメドレーでのElastiCache for Redisの運用周りのネタや知見について発表しました。本記事では、その中から抜粋してメモリ周りの話について紹介します。

キー削除周りの仕様について

キャッシュとしてElastiCache for Redisを利用していく上でまず把握しておきたいのが、キーの削除周りの仕様です。

Redis本家のExpiration/Eviction

ExpirationについてはRedisではキーにTTLを設定することで有効期限を設定することができ、こちら に記載のロジックで削除処理を実施してくれます。

Evictionについては Redis本家の実装では以下のような仕様になっています。Redis本家ドキュメントはこちら

  • used_memory(アプリが利用しているメモリ量)がmaxmemory値を超え始めたらEvictionが発火し始める
    • maxmemoryredis.conf 指定か CONFIG SETコマンドで設定できる
  • maxmemoryに到達したらどのように振る舞うべきかをmaxmemory-policyで指定
    • noeviction: 容量が必要なオペレーションではevitせず、エラーとなる
    • allkeys-lru: 常にLRUアルゴリズムで削除対象選定
    • volatile-lru: TTLが設定されたキー内でLRUアルゴリズムで削除対象選定
    • allkeys-random: 常にランダムに削除対象選定して削除
    • volatile-random: TTLが設定されたキー内でランダムに削除対象選定
    • volatile-ttl: TTLが設定されたキー内でTTL

また、ElastiCache for Redis ではまだ3系列までしか使えませんが、4系列では新たにLFUも選択肢に入ってくるようです。

Redis本家のEvictionの動作仕様としては以下のイメージです。

https://github.com/antirez/redis/blob/3.2.10/src/server.c#L3343-L3357

ElastiCache for Redis ではmaxmemoryは編集できず、その代わりに後述する reserved-memoryreserved-memory-percent)を設定するとEvictionが発火するメモリ量の閾値が変わることから上記周りの実装はAWS側で手を入れていそうです。

ElastiCache for Redis でのEviction

ElastiCache for Redis では以下のようにRedis本家にはないreserved-memoryという概念があるので注意が必要です。

  • maxmemoryインスタンスタイプによって固定値に設定されている
  • maxmemory変更できない
    • 代わりに reserved-memory or reserved-memory-percent を設定してEviction発火メモリ量を設定できる
    • maxmemory - reserved-memory < used_memory でEvictionが発火する
  • 古めのインスタンス(2017年3月16日以前作成)では reserved-memory がパラメータとして利用できるが、reserved-memory のデフォルト値は0byte
    • reserved-memory-percent のデフォルト値は25%
  • この周辺のAWS公式ドキュメントはこちら

ジョブメドレーでは単一のElastiCache for Redisインスタンスで運用対象を減らす戦略を取っています(Redis本家では用途別に分けて適切にチューニングすることを推奨しているため、将来的にはこの構成は変わるかもしれません)。そのため、キャッシュ利用のキーには必ずTTLを設定し、Eviction policyはvolatile-lruとしています。 そして、少し余裕をもたせて reserved-memory を設定、Evictions メトリクスの監視をしています。

メモリ利用量をSQLで分析する

現在稼働している本番環境上でどのパターンのキーがどの程度のメモリを使っているかを把握したくなる場面に出くわした方もいらっしゃるのではないでしょうか。

ElastiCache for Redis では簡単にRDBスナップショットを取得することができます。このデータをうまく使えば直接本番稼動のインスタンスを触りにいくことなく、手元で安心して分析作業が実施できます。

この分析作業に便利なのが redis-rdb-tools です。このツールはRDBファイルの内容をもとにキーごとのbyte値を以下のようなCSVに出力することができます(size_in_bytesは理論値)。

$ rdb -c memory dump.rdb > memory.csv;
$ cat memory.csv

database,type,key,size_in_bytes,encoding,num_elements,len_largest_element
0,list,lizards,241,quicklist,5,19
2,hash,baloon,138,ziplist,3,11

このcsvを以下のようにSQLite等のデータベースに突っ込むことで手元でSQLを使ってメモリ統計の分析ができるようになります。

$ rdb -c memory dump.rdb > memory.csv;
$ sqlite3 memory.db
sqlite> create table memory(database int,type varchar(128),key varchar(128),size_in_bytes int,encoding varchar(128),num_elements int,len_largest_element varchar(128));
sqlite>.mode csv memory
sqlite>.import memory.csv memory

ざっくりとした分析の流れは

  1. 本番環境のdaily backup RDB取得
  2. redis-rdb-toolscsv出力
  3. csvsqlitecsv formatでimport
  4. SQLで分析

のようになります。データベースにインポートさえ出来てしまえば以下のように様々な分析が可能になります。

sqlite> select sum(size_in_bytes) from memory where key like '%cells%';
XXXXX
sqlite> select count(*) from memory where key like '%cells%';
XXXXX

注意点としては実際に本番稼動しているメモリ量を取得しているわけではないので実測値と値がずれてくることです(感覚的には理論値は実測値の1/2ぐらいでした)。こちらの詳細の推定ロジックが気になる方は実装を確認いただければと思います。

ジョブメドレーではこの手法を使って分析することでアプリロジックを修正して不要なメモリ利用の改善等に役立てています。

まとめ

ElastiCache for Redis のメモリ周りを中心とした運用ネタについて紹介しました。

ElastiCache for Redis はとても便利でシュッと設定してそれなりの規模でもなんとなく運用できてしまう手軽さがありますが、油断していると思わぬ落とし穴にはまることもあります。

本記事がみなさまのElastiCache for Redis運用ライフの一助になれば幸いです。

また、ジョブメドレーをはじめ、メドレーの開発にご興味ある方は、ぜひご連絡ください。 www.medley.jp

メドレーがLTスポンサーを務めるRubyKaigi2018にもお邪魔する予定(たまにブースに立っています)ので、そちらでもお会いしましょう。 rubykaigi.org