athome-developer’s blog

不動産情報サービスのアットホームの開発者が発信するブログ

Visual StudioでC#とTypeScriptのUnitTestを一覧してみる。

ASP.NET WebForm系システムの駆逐まであと一歩。情報システム部の中嶋です。
弊社のシステム(特に社内向け)は、ASP.NET MVCASP.NET Coreで構築しているものが多数*1あります。そして、ASP.NET MVCの開発にはVisual Studioを使用*2しています。また、Web UIの開発を行っている以上、当然、.NET系の言語だけでは収まらずTypeScript*3も併用しながらの開発となります。
Visual Studio 2017は、C#などの.NET系の言語だけではなくTypeScriptでも定義や参照の検索が行え、インテリセンスも効くので非常に便利です。また、テストエクスプローラーではUnitTestの一覧が表示でき、一覧からテストを選択して実行することもできます。
が、しかし、テストエクスプローラーではTypeScriptのテストは表示されないため、TypeScriptのUnitTestだけはコマンドラインから実行することになります。
「なんか、ちょっともったいないな」と思っていたら、ありました。機能拡張が。マイクロソフトの井上章さんも紹介されている「Chutzpah Test Adapter for the Test Explorer(以下、Chutzpah)」です。
井上さんの記事は少し古くVisual Studio 2012でのお話なので、今回はVisual Studio 2017にChutzpahをインストールして使ってみたいと思います。

Chutzpahの動作概要

Chutzpahは、chutzpah.jsonで指定されたテスト対象コードおよびテストコードをテストハーネスとなるhtmlファイルから参照させ、それを同梱しているPhantomJS 2.1.1上で動作させています。 *4
テストハーネスとなるhtmlファイルは、設定ファイルで指定されたUnitTestエンジンをあらかじめ参照した形で自動的に生成されます。*5この時使用されるUnitTestエンジンは、Chutzpahに同梱されているQUnit 2.4.0、Jasmine 2.5.2、Mocha 3.2.0から選択することになります。*6

事前準備

まずテスト対象のシステムとして、ASP.NET MVCプロジェクトをUnitTest付きで用意しました。

TypeScriptのコードも用意しました。
ChutzpahSample/ChutzpahSample/TypeScript/base/core.ts*7

export class core {
    public static version = 8;
}

ChutzpahSample/ChutzpahSample/TypeScript/ui/ui.ts*8

import { core } from "base/core";

export class ui {
    public static displayVersion = "Version: " + core.version;
}

UnitTestエンジンにはJasmineを選択しました。またビルド時に依存関係を解決するものを用意しなかったので、PhantomJSにはRequireJSで解決してもらうことにしました。なお、これらはnpmで取得しています*9が、JasmineのjsファイルはChutzpahが同梱しているので型定義ファイルだけを取得しています。
ChutzpahSample/ChutzpahSample.Tests/package.json

{
  "name": "chutzpah-sample-test",
  "version": "1.0.0",
  "devDependencies": {
    "@types/jasmine": "~2.5.38",
    "requirejs": "^2.3.5"
  }
}

テストコードですが、Jasmineのjsファイルがビルド時に参照できる場所にないため、Jasmineだけreferenceタグで型定義ファイルを参照させています。
ChutzpahSample/ChutzpahSample.Tests/TypeScript/base/core.spec.ts*10

/// <reference path="../../node_modules/@types/jasmine/index.d.ts" />

import { core } from “../../../ChutzpahSample/TypeScript/base/core”;

describe("base/core", () => {
    it("will return correct version from core", () => {
        let version = core.version;
        expect(version).toBe(8);
    });
});

ChutzpahSample/ChutzpahSample.Tests/TypeScript/ui/ui.spec.ts*11

/// <reference path="../../node_modules/@types/jasmine/index.d.ts" />

import { ui } from "../../../ChutzpahSample/TypeScript/ui/ui";

describe("ui/ui", () => {
    it("will build display version", () => {
        let disp = ui.displayVersion;
        expect(disp).toBe("Version: 8");
    });
});

また、ChutzpahSample.Testsプロジェクト側のTypeScriptのコードがビルドされるように、ChutzpahSampleプロジェクト側の.csprojファイルから下記の2行をChutzpahSample.Testsプロジェクト側の.csprojファイルにコピーしました。

<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.Default.props" Condition="Exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.Default.props')" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.targets" Condition="Exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.targets')" />

さらに、RequireJSを使うため両プロジェクトのTypeScriptの設定を変更し、モジュールシステムに「AMD」を指定しました。

ちなみに、この状態でビルドを行っても、テストエクスプローラーにはC#のテストしか表示されません。

Chutzpahのインストール

そこでChutzpahをインストールします。Chutzpahのインストールは機能拡張から「Chutzpah Test Adapter for the Test Explorer」を選択し、行います。
f:id:athomeNakajima:20180217124546p:plain

Chutzpahの設定

次にchutzpah.jsonファイルを作成します。設定内容は主に下記の6つです。

  1. UnitTestエンジンにはJasmineを使用しているので、「Framework」を「Jasmine」に。
  2. TypeScriptのコンパイルはChutzpahでは行わずVisual Studio任せにしたいので、「Compile」の「Mode」を「External」に。
  3. テストコードのパスはchutzpah.jsonファイルからの相対パスになるので、「Tests」の「Path」を「TypeScript」*12に。
  4. RequireJSを使用するため、「TestHarnessReferenceMode」を「AMD」に。
  5. 「References」に書くパスを設定ファイルからの相対パスにするため、「TestHarnessLocationMode」を「SettingsFileAdjacent」に。
  6. RequireJSを使用するため、「References」にRequireJSのパス「node_modules/requirejs/require.js」を設定。

ChutzpahSample/ChutzpahSample.Tests/chutzpah.json

{
  "Framework": "jasmine",
  "Compile": {
    "Mode": "External",
    "Extensions": [ ".ts" ],
    "ExtensionsWithNoOutput": [ ".d.ts" ]
  },
  "Tests": [
    {
      "Path": "TypeScript",
      "Includes": [ "*.ts" ]
    }
  ],
  "TestHarnessReferenceMode": "AMD",
  "TestHarnessLocationMode": "SettingsFileAdjacent",
  "References": [
    {
      "Path": "node_modules/requirejs/require.js",
      "IsTestFrameworkFile": true
    }
  ]
}

上記のファイルを保存し、Visual Studioを再起動した後、ビルドを行うとテストエクスプローラーにTypeScriptのUnitTestも表示されるようになります。が、ui.spec.tsにあるUnitTestがまだ表示されません。

これはui.spec.tsとui.tsが異なるプロジェクトにありパスの起点が異なるため、ui.spec.tsから間接的に参照されるcore.tsが探せないためです。そこで、require_config.jsを作成して、参照するパスを変更します。なお、このファイルに記載するパスは、ChutzpahSample.Testsプロジェクトのルートからの相対パスになります。
ChutzpahSample/ChutzpahSample.Tests/TypeScript/require_config.js

require.config({
    paths: {
        "base": "../ChutzpahSample/TypeScript/base",
        "ui": "../ChutzpahSample/TypeScript/ui"
    }
});

そして、このrequire_config.jsをPhantomJSが参照するようにchutzpah.jsonを変更します。*13
ChutzpahSample/ChutzpahSample.Tests/chutzpah.json

{
  "Framework": "jasmine",
  "Compile": {
(中略)
  "References": [
    {
      "Path": "node_modules/requirejs/require.js",
      "IsTestFrameworkFile": true
    },
    {
      "Path": "TypeScript/require_config.js"
    }
  ]
}

これで無事にui.spec.tsにあるUnitTestもテストエクスプローラーに表示されるようになります。

もしうまく行かない場合には、chutzpah.jsonにおいて「EnableTracing」を「true」にし、「TraceFilePath」を「chutzpah.log」と設定すると、chutzpah.jsonと同じフォルダにログが出力されるようになります。特にファイルが見つからないなどのエラーが発生した場合に、どのパスを探しているかを確認する時に重宝します。

まとめ

今回は新規のプロジェクトにChutzpahを導入してみましたが、既存のプロジェクトでも(Visual StudioにChutzpahをインストールしておけば)設定ファイルを書くだけでテストエクスプローラーからJavaScript/TypeScriptのテストが実行できるようになります。
CIサーバーではコマンドラインでの実行が必要なので別の手立てを用意する必要がありますが、コードを書いているときはGUIからC#とTypeScriptのUnitTestを一度に起動できるので、なかなか重宝しています。皆さんも活用してみてはいかがでしょうか。

*1:ブログ記事の数を見ると.NET系が多数派のように見えますが、実は社外向けを含めると少数派だったりします。増やしたい。

*2:ASP.NET Coreの場合は、Visual Studio Codeだったりもします。

*3:.NET系のチームはTypeScriptですが、他のチームはCoffeeScriptだったりES2015だったりします。

*4:このためTypeScriptの出力をES2015(またはそれ以降)にしていると、出力されたJavaScriptをPhantomJSが実行できないため、テストは動作しません。

*5:設定ファイルで指定することもできます。

*6:Jasmineは、1.x系も同梱されています。

*7:このコードはChutzpahのサンプルコードを元にしています。https://github.com/mmanela/chutzpah/blob/master/Samples/RequireJS/TypeScript/base/core.ts

*8:このコードはChutzpahのサンプルコードを元にしています。https://github.com/mmanela/chutzpah/blob/master/Samples/RequireJS/TypeScript/ui/screen.ts

*9:だいたいproxyでやられます。

*10:このコードはChutzpahのサンプルコードを元にしています。https://github.com/mmanela/chutzpah/blob/master/Samples/RequireJS/TypeScript/tests/base/base.qunit.test.ts

*11:このコードはChutzpahのサンプルコードを元にしています。https://github.com/mmanela/chutzpah/blob/master/Samples/RequireJS/TypeScript/tests/ui/ui.qunit.test.ts

*12:通常の設定では、.tsファイルと同じフォルダにも.jsファイルと.js.mapファイルが出力されているため、この設定で動作します。

*13:chutzpah.jsonを書き換えた後は、Visual Studioの再起動が必要です。

ASP.NET Coreの設定変更を即時反映させる

こんにちは、情報システム部の高野です。
だいぶ間が空いてしまいました。継続は中々難しいですね!
最近は、実プロジェクトにべったりなのでHoloLensの検証が一向に進みません。
この記事も実プロジェクトをやっていて気付いたことです。

環境

.Net Core 2.0.5(SDK2.1.4)
ASP.NET Core2.0
テンプレートはMVCで作成

とりあえず普通に設定を利用してみる

ASP.NET Core のオプションのパターン | Microsoft Docs
公式見れば分かります・・・が、一応書いておきます。

設定(appsettings.json)をアプリケーションで利用するには
まず設定を入れておくクラスを作ります。

public class ApiConfig
{
    public string BaseUrl {get; set;}
}


それよりも前にappsettings.jsonに設定の追加が必要ですね。

"HogeApi": {
  "BaseUrl": "http://example.com/api/"
}

これを受けるのが上記のApiConfigクラスになります。


次にStartUpクラスのConfigureServicesメソッド内に下記を追加します。

services.Configure<ApiConfig>(Configuration.GetSection("HogeApi"));


最後に設定を利用したいクラスのコンストラクタの引数にIOptionsを追加します。

ApiConfig config;

public HomeController(IOptions<ApiConfig> option)
{
    config = option.Value;
}

これでHomeController内でURLを取得することができるようになりました。

アプリケーション起動中に設定を変更し即時反映させる

IOptionsを利用すると設定は読み込めるのですが、アプリケーションを再起動しないと
設定の変更が反映されません。

再起動せずに設定を変更させる方法も公式に載っています。
ASP.NET Core のオプションのパターン | Microsoft Docs
ここに載っているそのまま
IOptionsをIOptionsSnapshotに変更すればいいだけです。
(以前は、別途設定が必要だったような気が・・・新しいテンプレートだと無くなってました)

アクションフィルターで設定を利用する

アクションフィルターで設定を用いて処理したいこともありますよね(きっとあるはず)

まずアクションフィルターを作ります。
こちらもコンストラクタを作成して引数でIOptionsを取るようにします。
(即時反映したいならIOptionsSnapshot)

sealed public class TestFilterAttribute : ActionFilterAttribute
{
    ApiConfig config;

    public TestFilterAttribute(IOptions<ApiConfig> option)
    {
        config = option.Value;
    }
}


利用するコントローラーに属性として付与します。
ただしこの場合は、ServiceFilterを利用する必要があります。

[ServiceFilter(typeof(TestFilterAttribute))]
public class HomeController : Controller


StartUpクラスでインジェクションの設定をします。

services.AddScoped<TestFilterAttribute>();


これでアクションフィルターでも設定が利用できるようになったはずです。
上記にも書きましたがIOptionsSnapshotを使用して即時反映も可能です。

ミドルウェアでも設定が使いたい

ミドルウェアでも設定を使いたい時はあるでしょう(あるある)
ということでミドルウェアでもやってみる。

まずミドルウェアを作ります。
これも同じようにIOptionsをコンストラクタの引数で取ります。

public class TestMiddleware
{
    readonly RequestDelegate next;

    ApiConfig config;

    public TestMiddleware(RequestDelegate next, IOptions<ApiConfig> options)
    {
        this.next = next;
        config = options.Value;
    }

    public async Task Invoke(HttpContext context)
    {
        await next.Invoke(context);
    }
}


あとは、StartUpクラスでミドルウェアを登録するだけです。

app.UseMiddleware<TestMiddleware>();


これでミドルウェアでも設定が利用できるようになりました。
即時反映したいならIOptionsSnapshotを使います・・・とはいかないのです!
ミドルウェアのIOptionsをIOptionsSnapshotに変更すると下記のエラーが発生します。

An unhandled exception of type 'System.InvalidOperationException' occurred in Microsoft.AspNetCore.Hosting.dll: 'Cannot resolve scoped service 'Microsoft.Extensions.Options.IOptionsSnapshot`1[OptionsStudy.ApiConfig]' from root provider.'

どうやらScopedで作成したServiceはミドルウェアでは利用できないようです。
IOptionsSnapshotはAddScopedメソッドで登録されています。
IOptionsはAddSingletonメソッドでした。

それでは、ミドルウェアではIOptionsSnapshotは利用できず即時反映はできないのか?
ところができるのです。

コンストラクタの引数を消してInvokeメソッドの方に移すだけでした。
(これが分からず苦労した)

public async Task Invoke(HttpContext context, IOptionsSnapshot<ApiConfig> options)
{
    var conf = options.Value;
   // ・・・
    await next.Invoke(context);
}

まとめ

IOptionsSnapshotをインジェクションすればアプリケーションを再起動しないでも
設定の変更を反映することができる。
でもミドルウェアは、コンストラクタの引数ではなくInvokeメソッドの引数なので注意!




弊社ではエンジニアを募集しています。
興味がある方は下記からエントリお願いします。
athome-inc.jp

クリスマス

クリスマスが近づき、街も華やぐ今日この頃、

情報システム部のフロアの一角がクリスマス仕様になりました。

 

f:id:akhr1219:20171207131942j:plain

 

こんにちは 情報システム部の赤堀です。

当記事では、この一角のことやクリスマス準備の風景についてご紹介していきます。

 

 

リフレッシュコーナーができた!

先月フロアのレイアウト変更が行われ、仕事中の息抜きの場となるリフレッシュコーナーができました。

 

ですが!

 

f:id:akhr1219:20171207113337j:plain

机と椅子だけでちょっと寂しい……?

 

f:id:akhr1219:20171207130356j:plain

当部署いやし役のペッパーも、こんな日記を書いちゃう。(※休日だから)

 

 

リフレッシュコーナーを良い感じにしていこう!

リフレッシュコーナーを快適にするため、リフレッシュしやすい環境を作るため、

リフレッシュコーナー運営委員会が発足されました。

 

そして紆余曲折ありまして、委員会活動 第一弾、

リフレッシュコーナーをクリスマス仕様に飾り付けようの会が開かれることになったのです。

 

 

f:id:akhr1219:20171207113249j:plain

委員会メンバーと有志で飾り付けていきます。

 

f:id:akhr1219:20171207113325j:plain

ペッパーも嬉しそう。

 

f:id:akhr1219:20171207113024j:plain

どんどん飾りついていきます。

 

 

 

さて、クリスマスの飾り付けの中にはみんなで手作りした箇所があります。

それは靴下です。

 

f:id:akhr1219:20171207130722j:plain

 

こちらアドベントカレンダー(※日ごとの記事を書かないほう)を模していまして、

赤い靴下がどんどん緑に裏返されていくことでクリスマスまでのカウントダウンを演出しています。

 

靴下の中にはちゃんとお菓子も入ってます。

 

 

ただ靴下のサイズの都合上、お菓子は横から取り出します。

本当に靴下かな?

f:id:akhr1219:20171207113037j:plain

 

 

この靴下は、アドベントカレンダーをみんなで手作りしたい!お菓子を食べたい!

などなどのお話をきっかけに作られました。

普段パソコンばかり眺めているので布を切ったり貼ったりするのは良い気分転換です。

まさにリフレッシュ……?

 

 

でもこれでリフレッシュできたのはまだまだ一部です!

 

より多くの人のリフレッシュを目指して、リフレッシュコーナー運営委員会、今後どんどん活動していきます!

 

 

それではこのあたりで記事を終えたいと思います。

 

 

おわり

RHEL7.4でASP.NET Coreを動かす

こんにちは、情報システム部の高野です。
以前、下記の記事を書きまして今度はRHELだって環境構築したら
またもやハマったので手順を書いておきます。*1
dblog.athome.co.jp

環境

Red Hat Enterprise Linux Server release 7.4 (Maipo)
.Net Core2.0.0 (rhel.7-x64)

.Net Coreのインストール

ここを見れば分かりますが、一応

> sudo subscription-manager repos --enable=rhel-7-server-dotnet-rpms
> sudo yum install scl-utils
> sudo yum install rh-dotnet20
> scl enable rh-dotnet20 bash

※上記は、サーバ版のRHELのコマンドになります。ワークステーション版などはコマンドが違うので注意が必要です。

確認してみる。下記のように表示されればOKです。

> dotnet --info
.NET Command Line Tools (2.0.0)

Product Information:
 Version:            2.0.0
 Commit SHA-1 hash:  cdcd1928c9

Runtime Environment:
 OS Name:     rhel
 OS Version:  7
 OS Platform: Linux
 RID:         rhel.7-x64
 Base Path:   /opt/rh/rh-dotnet20/root/usr/lib64/dotnet/sdk/2.0.0/

Microsoft .NET Core Shared Framework Host

  Version  : 2.0.0
  Build    : N/A


注意が必要なのが、下記のコマンドです。

> scl enable rh-dotnet20 bash

これで「dotnet」コマンドがどこでも起動できるようになるのですが
ターミナルを起動するたびに入力が必要になります。

永続的にコマンドを使いたければ
/etc/profile.d/enabledotnet20.shを作成します。
中身は下記

source scl_source enable rh-dotnet20


ターミナルを再起動せずに利用したい場合は、下記コマンドを実行します。

source /etc/profile.d/enabledotnet20.sh

Nginxのインストール

Nginxのインストールと設定は、以前のCentOSの記事と同様です。

Kestrelをバックグラウンドで実行する

CentOSとパスが違うので記載しておきます。

/etc/systemd/systemに○○.serviceというファイルを作成します。(○○は、なんでもいいです)

[Unit]
  Description=kestrel server
  After=syslog.target network.target
 
[Service]
  ExecStart=/opt/rh/rh-dotnet20/root/bin/dotnet /home/user/www/hoge/sample.dll #dotnetのパスが異なる
  WorkingDirectory=/home/user/www/hoge
  Restart=always
  RestartSec=10
  SyslogIdentifier=sampleapp
  User=user
  Environment=ASPNETCORE_ENVIRONMENT=Staging #サイト毎に環境を変えたい時などに使える
 
[Install]
WantedBy=multi-user.target


起動します。

> sudo systemctl start ○○.service


サーバー再起動時にKestrelも起動したい場合

> sudo systemctl enable ○○.service

デプロイする

dotnet publishコマンドでデプロイ用のモジュールを作成します。

dotnet publish -o /home/www/hoge/ -c Release -r rhel.7-x64

「-r」 オプションで「rhel.7-x64」を付けないとダメです。

まとめ

RHELASP.NET Core環境の作り方を説明しました。
CentOSとは、微妙に違う部分があるので注意が必要です。


弊社ではエンジニアを募集しています。
興味がある方は下記からエントリお願いします。
athome-inc.jp

*1:いつもこのパターンですいません。

バージョン管理システムの比較(という名のポエム)

情報システム部の中嶋です。弊社では以前よりバージョン管理システムVCS)としてGitを採用していますが、Subversion歴が長い*1おっさんとしては、ここで一度ちゃんと比較をしておきたいと思い、4つの論点で比較してみました。(まぁ、控えめに言ってポエムです。)
なお、「チェンジセット記録型」とか「タグ型ブランチ」とかの用語はこの文書のために作った造語なので、あまり気にせずに読み飛ばしていただけると幸いです。

TL;DR

悲観的排他かつ集中リポジトリ 楽観的排他かつ集中リポジトリ 楽観的排他かつ分散リポジトリ
チェンジセット記録型かつコピー型ブランチ VSSなど Subversion、TFVCなど (たぶんない)
スナップショット記録型かつタグ型ブランチ (たぶんない) (たぶんない) Gitなど

「SubversinよりGit」と言う人は、超巨大プロジェクトを扱う機会がなく、マージ先を柔軟に選択できるからGitを選んでいるのではないかと思う。

論点1:悲観的排他と楽観的排他

悲観的排他とは

悲観的排他とは、ファイルの更新をそのファイルの更新権(ロック)を取得してから行うことにより、同時に複数人が同じファイルを更新してしまうことを抑止する方法である。ロックを取得することを「チェックアウト」、更新内容をリポジトリに格納しロックを手放すことを「チェックイン」と呼ぶことが多い。有名なVCSとしては、Visual SourceSafe(VSS)などがある。

楽観的排他とは

楽観的排他とは、「同時に複数人が同じファイルを更新することは稀なので、コンフリクトしたら(自分が修正したファイルを他人も修正してすでにコミットしていたら)その時に考える」という方法である。通常、コンフリクトした場合には、マージを行なってから再度コミットする。一般的に、更新内容をリポジトリに格納することを「コミット」と呼ぶが、リポジトリに格納した更新内容自体をコミットと呼ぶVCSもある。また「チェックアウト」という用語も「作業場の内容を最新に更新する」的な意味で用いられるが、詳細はVCSによって異なる。
近年の楽観的排他を用いたVCSは、コンフリクトが発生した場合にも自動的にマージすることができるようになってきたため、編集者がコンフリクトを解決する場面は少なくなってきている。有名なVCSには、Subversion*2やTeam Foundation Version Control(TFVC)、Gitなどがある。
なお、Subversion*3やTFVCは悲観的排他もサポートしている。

楽観的排他に対する悲観的排他の利点

絶対にコンフリクトが起きないので、マージの必要がない。よって、マージミスは絶対に発生しない。また、Excelファイルなどテキスト形式でないファイルはマージが行えない(もしくは難しい)ため、初めからコンフリクトが起きないようにしておくことは有効な方法である。

悲観的排他に対する楽観的排他の利点

ロックの取得が不要であるため、「他人が更新中であるため、自分が更新できない」という事態が発生しない。また、ロックを取得したまま長期間放置する不心得者への対処が不要である。

論点2:集中と分散

集中リポジトリ型とは

集中リポジトリ型とは、中央にある1つのリポジトリを複数の編集者が共有する方法である。有名なVCSには、VSS、Subversion、TFVCなどがある。

分散リポジトリ型とは

分散リポジトリ型とは、編集者個々がそれぞれリポジトリを所有し、必要に応じてリポジトリ同士を同期させる方法である。一般的には、中央リポジトリを1つ用意し、各編集者はそのリポジトリのコピー(クローン)を手元に作成して使用する。「プッシュ」や「プル」などクローン元のリポジトリと同期するための操作がある。有名なVCSには、Git、Mercurial、BitKeeperなどがある。

分散リポジトリ型に対する集中リポジトリ型の利点

リポジトリをクローンする必要がない。極端に大規模なプロジェクトでなければ問題となることはないが、実際にWindows OSやOfficeなどはリポジトリが巨大であるために分散リポジトリ型への移行が行えずにいた。Microsoftはこの問題を解決するためにGit専用のファイルシステムまで開発することになった*4。また、原理的に悲観的排他は集中リポジトリ型でなければ実現できない*5

集中リポジトリ型に対する分散リポジトリ型の利点

リポジトリは必ず編集者の手元にあるため、オフラインな環境であってもリポジトリにアクセスできる。また、試行錯誤の過程をリポジトリに格納しながら他人には公開しないといったことも行える。
ファイルの更新履歴の表示なども、リポジトリが手元にあるため高速に行うことができる。ただし、ここで表示されるファイルの更新履歴は編集者の手元のリポジトリに記録されているものであるため、必ずしも中央リポジトリと同じとは限らない。

論点3:チェンジセットとスナップショット

チェンジセット記録型とは

チェンジセット記録型とは、コミットの際、リポジトリに現在の状態と直前の状態との変更差分を記録する方法である。リポリトリに格納する操作を「コミットする」、コミットする対象を「チェンジセット」と呼ぶ場合が多い。有名なVCSに、SubversionやTFVCなどがある。
なおVSSも変更差分を記録しているが、同時に行われた複数のファイルに対する変更を1つのものとしては認識せず、ファイル単位で管理している。

スナップショット記録型とは

スナップショット記録型とは、コミットの際、リポジトリに管理対象ファイル全ての現在の状態を記録する方法である。リポジトリに格納する操作も格納する対象も「コミット(する)」と呼ぶ。この方式を採用している代表的なVCSにはGit*6がある。

スナップショット記録型に対するチェンジセット記録型の利点

スナップショット記録型のVCSではファイル名の変更を記録することができない*7。Gitではこの問題を回避するために、「削除されたファイルと追加されたファイルの内容が非常に似ている場合、それはファイル名が変更されたものだ」と判断しUIに表示している。
また、スナップショット記録型は必ず管理対象ファイル全てを同時に扱うため管理対象ファイルの一部だけをチェックアウトすることができないが、チェンジセット記録型では1つのリポジトリのうち必要な部分だけをチェックアウトし作業を行うことができる。このため、チェンジセット記録型はスナップショット記録型よりも大規模プロジェクトに向いていると言える。
さらにスナップショット記録型は各時点でのファイルのスナップショットを保持しているため、チェンジセット記録型よりも格納しているデータ量が多く、特に大きなファイルが存在する場合、リポジトリの肥大化が顕著である。

チェンジセット記録型に対するスナップショット記録型の利点

チェンジセット記録型のVCSでは1つ1つのコミットが変更差分しか持っていないため、1つのコミットだけを独立して操作することはできない。しかしスナップショット記録型のVCSは、そのリポジトリが管理対象としている全てのファイルを1つのコミットの中に保持しているため、1つのコミットだけを独立して操作することができる。これによりコミット順序の変更やコミット単位の調整、分岐元/分岐先以外へのマージが行えるようになっている。

論点4:コピーとタグ

コピー型ブランチとは

コピー型ブランチとは、ブランチの作成を「リポジトリ内でのファイルコピー」として扱い記録する方法である。有名なVCSに、Subversion*8やTFVCなどがある。
なおVSSもコピー型ブランチを採用していると言えるが、ブランチを作成するという行為自体が悲観的排他の利点を損なう行為であるため、利用場面は限られる*9

タグ型ブランチとは

タグ型ブランチとは、ブランチを「コミット指し示すポインタ(ここではタグと表現する)」として扱い記録する方法である。この方式を採用している代表的なVCSには、Git*10がある。

タグ型ブランチに対するコピー型ブランチの利点

コピー型ブランチでは、1つの作業領域に1度に複数のブランチを展開することができる。このため、あるブランチで作業中に他のブランチの作業を行う際、作業中の修正をコミット/退避する必要がない。
また、試験的な実装を行なっている複数ブランチのプログラムを同時に実行するなど、柔軟な対応が行える。これに対し、タグ型ブランチでは1つのブランチしか展開することができない。1つのファイルシステム上に複数のブランチを同時に展開するためには、リポジトリ自体をクローンする必要がある。

コピー型ブランチに対するタグ型ブランチの利点

タグ型ブランチを採用しているVCSにおいて「ブランチの作成」とは、すなわち「タグの作成」であるため、その処理は非常に高速である*11。また、ブランチの内容が作成時に固定化されないため、ブランチの分岐位置を作成後に容易に変更することができる。さらにマージに関しても、マージ先のブランチが指すコミットがマージ元のコミット履歴に含まれる場合はマージ先ブランチが指すコミットを変更するだけで高速かつ確実にマージを行うことができる。


ということで、一通りまとめてみました。後発のシステムほどそれまでのシステムが抱えている問題を解決した良いものになっているわけですが、「人は何かの犠牲なしに何も得ることはできない。」ので多かれ少なかれトレードオフがあったりします。
「じゃぁ、どれを採るか」ということですが、コードレビューをしっかりやるとどうしても変更履歴が無駄に増えてしまうので、ここを変更できるGitが今の所一番使いやすいのかなぁと思っています。


2017年9月8日
公開当初、Mercurialも比較対象に含めておりましたが、内容に誤りがございましたので削除いたしました。

*1:自分は2017年4月にアットホームにJoinしましたが、それまではSubversionやTFVCを使用していました。

*2:http://jtdan.com/vcs/svn/svn-book/svn.basic.vsn-models.html#svn.basic.vsn-models.copy-merge

*3:http://jtdan.com/vcs/svn/svn-book/svn.advanced.locking.html

*4:https://www.infoq.com/jp/news/2017/02/GVFS

*5:Mercurialにはその名もズバリ「Lock Extension」という分散なのに悲観的排他が実現できるエクステンションがあるそうなので、「原理的に〜実現できない」は大げさな表現かもしれません。

*6:https://git-scm.com/book/ja/v1/使い始める-Gitの基本

*7:Subversionも、リポジトリがVer.1.8になるまでは変更が追跡できないことが多かった記憶があります。Ver.1.8になってから、変更の追跡もマージも一気に優秀になりました。

*8:http://jtdan.com/vcs/svn/svn-book/svn.branchmerge.using.html

*9:VSSでも「ファイルの共有」を使用することでバージョン別のブランチを悲観的排他で運用することが可能ですが、マージが前提となるトピックブランチのような運用は想定されていないと思われます。

*10:https://git-scm.com/book/ja/v2/Git-のブランチ機能-ブランチとは

*11:実際にはSubversionにおけるブランチ作成も「このリビジョンからこのブランチを作成した」という情報だけをコミットしているので、そこまでの速度差はないと思われます。

画像システムのリプレイスについて基調講演を行いました

情報システム部の河野です。 入社・開発2年目で、現在はRuby on Rails を使った開発を担当しています。

本日は2017年7月26日(水)に行われました、

ZDNet Japan × TechRepublic Japan & AWS Partner Network 5週間連続セミナーシリーズ データベース編 導入事例から考える–なぜ今、クラウドDBが注目されるのか? の内容について書きたいと思います。

今回このイベントにて、弊社の取り組みについて弊社の鈴木が基調講演をさせて頂きました! 「OracleからAuroraへの移行とオンプレミスとの連携」と題しまして、2016年に行った画像システムのOracle DBのオンプレミス環境からAWSへの移行を行った際の経緯や検討事項、本稼働後の課題についてご紹介しました。

ご存じの方も多いとは思いますが、弊社アットホームは不動産会社と物件を探す方をつなぐ不動産情報サービスを展開しています。 今では物件を探し検討する際、物件の画像がついているのは当たり前とも言える時代です。 弊社の各種サービスでも画像を利用したものは多くあり、その物件画像の配信サービスを支えているシステムは弊社のサービスを支える柱の一つとも言えます。

画像システムの移行と結果

さて、前述にもあります通り画像の需要が増えたことで、画像配信システムのリクエストは1億数千万件/日に上り、キャッシュを利用してもリクエストの件数は増加の傾向にありました。

それにより弊社のシステムには、運用・保守費用の増加や、24時間365日の稼働を行うための安定性確保の課題が浮上してきました。

具体的には

  • Oracleが接続するSANのIO性能の限界
  • ストレージの容量追加やDBサーバのメモリ追加の度に作業が発生(数ヶ月に一度のメンテナンス)
  • ライセンスの上限
  • データ増加に伴いSANの費用が増加(月額課金のサービスを利用)

などです。

そこで2016年7月より、Oracle DBからAmazon Auroraへの移行を行い新システムの運用を開始しました。 弊社がAuroraを採用した理由としては

  • 高可用性
  • 高耐久性
  • 運用の手間が少ない
  • MySQL5.6互換
  • 拡張性

などがあります。

本稼働後の状況

パフォーマンス面では、参照性能はOracle RACとほぼ同等の性能を発揮。大量更新などの際に発生していた、性能劣化が改善しました。

可用性の面では、パラメータチューニングがほとんど不要で、容量追加作業が無くなり、費用面においては、旧システムでかかっていた費用と比較すると半額程度(推定)になりました。

以上のことから、旧システムに比べて随分と楽になった、やってよかった!という結果になりました。

アットホームでは新しい技術を積極的に取り入れるチャレンジをしています。

現在も弊社サービスのリプレイスにて、AWSに移行を伴うプロジェクトも始動中です。

(私もこのプロジェクトに開発メンバーとして参加しています!)

弊社では一緒に開発を行ってくれるエンジニアを募集しています。

興味がある方は下記からエントリお願いします。 ↓↓↓ athome-inc.jp

ASP.NET Coreの環境はASPNETCORE_ENVIRONMENT未設定の場合Productionになる

情報システム部のやまだです。

大分久しぶりに書きますが、これを機にまた書き始めようかな、と。

さて、以前弊社の高野が下記の記事にて

dblog.athome.co.jp

なんでデフォルトがProductionなんだろうか? なにか変な設定が入っているのかな?

と書いていたのですが、その理由も含めてわかったので書き留めておきます。

What happened?

上記にもあるように、アプリケーションの設定をappsettings.jsonに記載してありまして、ステージングや本番など環境ごとにさらに下記のようなファイルを作成していました。

  • ステージング環境(appsettings.staging.json)
  • 本番環境(appsettings.production.json)

appsettingsのあとのファイル名は環境変数のASPNETCORE_ENVIRONMENTの値で決まり、値がstagingならばappsettings.jsonに加えてappsettings.staging.jsonも読み込まれるという、環境変数によって設定を変えるというよくある感じです。RailsだとRAILS_ENV、ExpressだとNODE_ENVですね。

で、ある日、IISで設定したはずのASPNETCORE_ENVIRONMENTの設定が消えていて、気が付いたら本番の設定が適用されていて危機一髪ということがありました。

why?

そもそもIISの設定が無くなったのかはともかくとして、

「そもそもデフォルトってProductionなの?」

「なぜデフォルトがProductionなの?」

ということでちょっと調べて(ググって)みたら見つかりました。

デフォルトってProductionなの?

下記の通りProductionでした。

Hosting/HostingEnvironment.cs at dev · aspnet/Hosting · GitHub

なぜデフォルトだとProductionなの?

TL; DR セキュリティ的によろしくないからProductionにしている

なんでDevelopmentじゃなくてProductionなの?

RailsやExpressはDevelopmentなのに。

ということでさらにググってみたところ、なんかそのものズバリなissueを発見。

Why is hosting environment default value "production"? · Issue #863 · aspnet/Hosting · GitHub

以下、関係ありそうな部分を引用してみます。

blowdart commented on 14 Oct 2016
So, the reason we went with production by default is because both our templates, and in general other people's code add detailed error messages, debug logging, and other things that can cause information disclosure vulnerabilities (at best), or things like auth bypass at worst, depending on what you wrapped in an env.IsDevelopment() check.

By defaulting to production we remove that risk.

In addition launching from inside VS and VS Code sets the environment as development, and best practice generally acknowledges developers should not have every day access to production assets, the risk of defaulting to development is far greater than defaulting to release/production.

 よし、翻訳だ。

ブローダート はコメントしました on 14 Oct 2016
だから、我々がデフォルトでプロダクションを行ったのは、テンプレートと一般的に他の人のコードの両方が、詳細なエラーメッセージ、デバッグログ、および情報漏えいの脆弱性を引き起こす可能性のあるもの、あなたがenv.IsDevelopment()チェックでラップした内容に応じて、最悪です。

デフォルトでは、プロダクションはそのリスクを排除します。

さらに、VSとVSコードの内部からの起動は、開発環境として環境を設定し、開発者が毎回運用資産にアクセスする必要がないことを一般的に認めているため、開発の不履行リスクはリリース/生産のデフォルトよりもはるかに大きくなります。

デバッグ情報とかだだもれしちゃうから安全な方に振っておくよ、ということですね。

ほか、こちらのコメントの方が端的でわかりやすいかもしれません。

DefaultEnvironmentName should be development · Issue #712 · aspnet/Home · GitHub

blowdart commented on 1 Jul 2015
Yes, but your example of web.config is precisely why files don't work. People didn't build in release and publish because it wasn't the default. The amount of debug web sites, with detailed error messages out there is awful. 

ブローダート はコメントしました on 1 Jul 2015
はい。しかし、web.configの例は、ファイルが機能しない理由です。人々はデフォルトではなかったので、リリースでビルドして公開しませんでした。詳細なエラーメッセージが表示されたデバッグWebサイトの量はひどいです。

あー、なるほど。

この辺の方針というか思想というかデザインというかは、言語やフレームワークによって違いそうなので、気を付けたいですね。