ITエンジニア育成担当の研究ブログ

社内のITエンジニア向け講座企画・運営+たまに開発な仕事の中で試してみたことなど。

【Xamarin.Forms+Cognitive Services】飯テロ防止Twitterクライアント作ってみた

はじめに

はやいものでもう11月になってしまいました。今年もあと2か月弱。
研修などが土曜に多い関係上、なかなか土曜に休むことができない日々が続き、土曜の面白そうな技術系イベントにあまり出られず、ちょっと最近フラストレーションがたまっています。
その憂さ晴らしついでに、今回はちょっと手を動かしてみました。

今回作ったもの

タイトルにある通り、「飯テロ防止Twitterクライアント」です。
Twitterで食べ物の写真をたくさん上げている人をミュートした」ということを言っている後輩がいて、たしかに食べ物の写真は、深夜だったり気分の悪いときに見るのはきついとは思うですが、ミュートしてしまうことで食べ物写真ツイート以外の他の有益な情報まで目に入らなくなってしまうのはもったいないことです。
だったら、食べ物の写真だけうまいこと表示されないような「飯テロ防止」機能を持ったTwitterクライアントがあればよいのでは?と思ったのがはじまりです。
特に実運用は想定していないので、今回は飯テロ防止に関係がある「タイムライン表示」の部分だけ実装しています。

ソースはGitHubに公開しています。
github.com

名前は「FoodPornPreventer」。
preventerはちょっと微妙かもですが、英語で飯テロに近い言葉は「food porn」と言うそう。

アプリのしくみ

まず、Twitterクライアントなので、スマホなどモバイル端末での利用を前提に、Xamarin.FormsでAndroid/iOS/UWPに対応させています。
そこにTwitterクライアントとしての機能を実装するため、「CoreTweet」というパッケージを導入しています。Twitter連携するアプリを作成するうえでの定番のようです。
CoreTweetはこちら。日本語の説明がありました。 github.com

肝心の「飯テロ防止」は、AzureのCognitive Servicesにある「Computer Vision API」による画像解析を用います。
画像を解析してさまざまなことを教えてくれるサービスです。
azure.microsoft.com

流れは、

  1. CoreTweetでTwitterのタイムラインを取得

  2. 画像を含むツイートの場合、その画像URLをComputer Vision APIに渡して解析

  3. 解析結果に「food」という文字列が含まれていれば“飯テロ”とみなし、警告画像に差し替え、それ以外はそのままタイムラインに表示させる

となります。 では実際に中身を見ていきます。

Twitter連携

CoreTweetを導入するには、NuGetでPCLプロジェクトに「CoreTweet」をインストールします。
CoreTweetを入れると「Newtonsoft.Json」のパッケージも一緒に入るのですが、これが最新の「10.x」だとUWPで動きませんでした。なので、最終的にバージョンはCoreTweetが「0.8.1.394」、Newtonsoft.Jsonが「9.0.1」の組み合わせにしています。
また、ターゲットを変更しないとうまく上記がインストールできなかったので、Profileは「7」に変更しています。

では、CoreTweetによるタイムラインの取得処理です。

以下のように生成したアクセストークンを使って、

// トークンの生成
// 今回は事前にTwitterアカウントで作成しておいたアクセス情報を使用
private Tokens Tokens { get; } = Tokens.Create(Secrets.ConsumerKey, Secrets.ConsumerSecret, Secrets.AccessToken, Secrets.AccessTokenSecret);

タイムライン取得を行います。

var timeline = await Tokens.Statuses.HomeTimelineAsync(count: TweetCount, max_id: maxId);

これはTwitter APIの「statuses/home_timeline」に相当します。CoreTweetはこのように、基本的にTwitter APIをそのままC#で呼べるよう実装されているので、Twitter APIさえわかればそれと同じような呼び出し方で使うことができ、とても楽です。

取得したツイートのデータは適当にクラスに詰めてListViewにバインドさせて表示させています。
引用リツイートとか動画とか、そのあたりは今回の本題から外れるのであまり作りこんでいません。

Computer Vision APIでの画像判定

食べ物かどうかの判定ですが、あらかじめAzureでComputer Vision APIを使うためのキー情報を取得しておきます。
Xamarinから呼び出すのは簡単で、NuGetからMicrosoft.ProjectOxford.Visionを導入すればOK。
これでTagsを調べ、飯テロかどうかを判定します。Tagsの要素内のHintまたはNameはが「food」であれば食べ物画像=飯テロとみなします。

private async Task<bool> IsFoodPornAsync(string url)
{
    try
    {
        // Computer Vision APIで解析してTagsを取得
        var vision = new VisionServiceClient(Secrets.VisionSubscriptionKey, Secrets.VisionApiRoot);
        VisualFeature[] features = { VisualFeature.Tags };
        var result = await vision.AnalyzeImageAsync(url, features.ToList());
        return result.Tags.Any(t => t.Hint == "food" || t.Name == "food");
    }
    catch (Exception ex)
    {
        return false;
    }
}

判定処理はわずか4行。お手軽ですね。

サムネイル画像の差し替え

飯テロ画像だったら警告画像に差し替えます。
今回はConverterを使って、Computer Vision APIによる飯テロ判定のフラグ値で切り分けています。

public class ImageMaskConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var imageInfo = value as TweetImageInfo;

        if (imageInfo == null) return null;

        // 飯テロ画像なら画像を差し替え
        return imageInfo.IsFoodPorn
            ? ImageSource.FromResource("FoodPornPreventer.Images.mark_chuui.png")
            : ImageSource.FromUri(new Uri(imageInfo.Url));
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

実際の動作

今回の開発にあたり、食べ物画像BOT@ibukurogさんのツイートをテストに使わせていただきました。
たとえば、このツイート。

このツイートを本アプリを通して見ると…

Android

f:id:himanago:20171105011730p:plain:w300

iOS

f:id:himanago:20171105011745j:plain:w300

UWP

f:id:himanago:20171105001349p:plain:w300

上記のように、うっかり飯テロにあうことを防ぐことができます!
ちなみに差し替えたアイコンはいらすとやさんからいただいたものです。
注意のマーク | かわいいフリー素材集 いらすとや

なお、iOSだけ、ツイートの追加読み込みでスクロール位置が戻ったり項目が上下に動いて一瞬重なって見えたりと、ちょっと変な動きをするのでいずれ直したいです。

苦労したところ

ListViewはネストできない

ツイートをListViewで並べて表示しますが、各ツイートのListItemに画像を並べる際、ListViewをネストできたら動的に画像の数が変わってくれると思ったのですが、ListViewはネストすることができなかったので、断念。Twitterでは1ツイートには画像が最大4枚という仕様なので、しかたなく4枠固定で画像の領域を用意してごまかしました。

iPhoneでの実機確認時のエラー①

iPhoneの実機で動かしたときがけっこう苦戦しました。
まず、ビルド時に以下のようなエラーが。
f:id:himanago:20171104234147p:plain

Invalid architecture: ARMv7. 32-bit architectures are not supported when deployment target is 11 or later.

端末はiPhone8(iOS11)。おそらく、iOS11から完全64bit化したため「32bitは使えないよ」というエラーが出たのだと思われます。ここは、iOSプロジェクトのビルド設定でサポートされているアーキテクチャを「ARMv7 + ARM64」から「ARM64」に変更すると解消しました。 f:id:himanago:20171104234206p:plain

あとその下の「すべての32ビット浮動小数点演算を64ビット浮動小数点演算として実行する」のチェックも入れておく必要がありそう。
f:id:himanago:20171105100753p:plain

iPhoneでの実機確認時のエラー②

もうひとつ、実機デバッグ時のみ、Computer Vision APIを呼び出す際に例外が発生しました。シミュレーターを使ったときやAndroid/UWPでは発生しなかったものだったので、最初は「?」でした。

  at System.Linq.Expressions.Expression.Call (System.Linq.Expressions.Expression instance, System.Reflection.MethodInfo method, System.Collections.Generic.IEnumerable`1[T] arguments) [0x00111] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.2.0.11/src/mono/external/corefx/src/System.Linq.Expressions/src/System/Linq/Expressions/MethodCallExpression.cs:1239 
  at System.Linq.Expressions.Expression.Call (System.Linq.Expressions.Expression instance, System.Reflection.MethodInfo method, System.Linq.Expressions.Expression[] arguments) [0x00000] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.2.0.11/src/mono/external/corefx/src/System.Linq.Expressions/src/System/Linq/Expressions/MethodCallExpression.cs:1046 
  at System.Linq.Expressions.Expression.Call (System.Reflection.MethodInfo method, System.Linq.Expressions.Expression[] arguments) [0x00000] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.2.0.11/src/mono/external/corefx/src/System.Linq.Expressions/src/System/Linq/Expressions/MethodCallExpression.cs:1001 
  at System.Dynamic.ExpandoObject+MetaExpando.BindSetMember (System.Dynamic.SetMemberBinder binder, System.Dynamic.DynamicMetaObject value) [0x00033] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.2.0.11/src/mono/external/corefx/src/System.Linq.Expressions/src/System/Dynamic/ExpandoObject.cs:863 
  at System.Dynamic.SetMemberBinder.Bind (System.Dynamic.DynamicMetaObject target, System.Dynamic.DynamicMetaObject[] args) [0x00035] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.2.0.11/src/mono/external/corefx/src/System.Linq.Expressions/src/System/Dynamic/SetMemberBinder.cs:57 
  at System.Dynamic.DynamicMetaObjectBinder.Bind (System.Object[] args, System.Collections.ObjectModel.ReadOnlyCollection`1[T] parameters, System.Linq.Expressions.LabelTarget returnLabel) [0x000c6] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.2.0.11/src/mono/external/corefx/src/System.Linq.Expressions/src/System/Dynamic/DynamicMetaObjectBinder.cs:90 
  at System.Runtime.CompilerServices.CallSiteBinder.BindCore[T] (System.Runtime.CompilerServices.CallSite`1[T] site, System.Object[] args) [0x00019] in <773264786149499a986a13db6a7d46fe>:0 
  at System.Runtime.CompilerServices.CallSiteOps.Bind[T] (System.Runtime.CompilerServices.CallSiteBinder binder, System.Runtime.CompilerServices.CallSite`1[T] site, System.Object[] args) [0x00000] in <773264786149499a986a13db6a7d46fe>:0 
  at (wrapper managed-to-native) System.Reflection.MonoMethod:InternalInvoke (System.Reflection.MonoMethod,object,object[],System.Exception&)
  at System.Reflection.MonoMethod.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00032] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.2.0.11/src/mono/mcs/class/corlib/System.Reflection/MonoMethod.cs:305 
--- End of stack trace from previous location where exception was thrown ---
  at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x0000c] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.2.0.11/src/mono/mcs/class/referencesource/mscorlib/system/runtime/exceptionservices/exceptionservicescommon.cs:151 
  at System.Linq.Expressions.Interpreter.ExceptionHelpers.UnwrapAndRethrow (System.Reflection.TargetInvocationException exception) [0x00000] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.2.0.11/src/mono/external/corefx/src/System.Linq.Expressions/src/System/Linq/Expressions/Interpreter/Utilities.cs:174 
  at System.Linq.Expressions.Interpreter.MethodInfoCallInstruction.Run (System.Linq.Expressions.Interpreter.InterpretedFrame frame) [0x00035] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.2.0.11/src/mono/external/corefx/src/System.Linq.Expressions/src/System/Linq/Expressions/Interpreter/CallInstruction.cs:333 
  at System.Linq.Expressions.Interpreter.Interpreter.Run (System.Linq.Expressions.Interpreter.InterpretedFrame frame) [0x00015] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.2.0.11/src/mono/external/corefx/src/System.Linq.Expressions/src/System/Linq/Expressions/Interpreter/Interpreter.cs:63 
  at System.Linq.Expressions.Interpreter.LightLambda.Run3[T0,T1,T2,TRet] (T0 arg0, T1 arg1, T2 arg2) [0x00038] in <773264786149499a986a13db6a7d46fe>:0 
  at Microsoft.ProjectOxford.Vision.VisionServiceClient+<AnalyzeImageAsync>d__19.MoveNext () [0x00053] in <26ced5c9a33f420b960f06b6963aca5a>:0 
--- End of stack trace from previous location where exception was thrown ---
  at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x0000c] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.2.0.11/src/mono/mcs/class/referencesource/mscorlib/system/runtime/exceptionservices/exceptionservicescommon.cs:151 
  at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Threading.Tasks.Task task) [0x00037] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.2.0.11/src/mono/mcs/class/referencesource/mscorlib/system/runtime/compilerservices/TaskAwaiter.cs:187 
  at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Threading.Tasks.Task task) [0x00028] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.2.0.11/src/mono/mcs/class/referencesource/mscorlib/system/runtime/compilerservices/TaskAwaiter.cs:156 
  at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd (System.Threading.Tasks.Task task) [0x00008] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.2.0.11/src/mono/mcs/class/referencesource/mscorlib/system/runtime/compilerservices/TaskAwaiter.cs:128 
  at System.Runtime.CompilerServices.TaskAwaiter`1[TResult].GetResult () [0x00000] in <a89624c267f94034b6cf9aa0c56f8864>:0 
  at FoodPornPreventer.MainPage+<IsFoodPornAsync>d__11.MoveNext () [0x00050] in C:\Users\hoge\Documents\Visual Studio 2017\Projects\FoodPornPreventer\FoodPornPreventer\FoodPornPreventer\MainPage.xaml.cs:116 
--- End of stack trace from previous location where exception was thrown ---
  at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x0000c] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.2.0.11/src/mono/mcs/class/referencesource/mscorlib/system/runtime/exceptionservices/exceptionservicescommon.cs:151 
  at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Threading.Tasks.Task task) [0x00037] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.2.0.11/src/mono/mcs/class/referencesource/mscorlib/system/runtime/compilerservices/TaskAwaiter.cs:187 
  at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Threading.Tasks.Task task) [0x00028] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.2.0.11/src/mono/mcs/class/referencesource/mscorlib/system/runtime/compilerservices/TaskAwaiter.cs:156 
  at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd (System.Threading.Tasks.Task task) [0x00008] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.2.0.11/src/mono/mcs/class/referencesource/mscorlib/system/runtime/compilerservices/TaskAwaiter.cs:128 
  at System.Runtime.CompilerServices.TaskAwaiter`1[TResult].GetResult () [0x00000] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.2.0.11/src/mono/mcs/class/referencesource/mscorlib/system/runtime/compilerservices/TaskAwaiter.cs:357 
  at FoodPornPreventer.MainPage+<AddTweets>d__10.MoveNext () [0x003a2] in C:\Users\hoge\Documents\Visual Studio 2017\Projects\FoodPornPreventer\FoodPornPreventer\FoodPornPreventer\MainPage.xaml.cs:85 

調べてみると、どうやらリンカーの動作を「Don't Link」にすると解決するらしい、ということがわかりました。 www.syncfusion.com github.com

iOSプロジェクトのプロパティで以下のように変更。 f:id:himanago:20171105095756p:plain

すると、無事実機デバッグできるようになりました。
ただこれだとアプリのサイズが大きくなってしまうと思われるため、以下のようにほかの回避方法もありそうですし、もう少し調べてみたいなと思っています。 spiratesta.hatenablog.com

その他

Xamarinの開発をするときは、普段メインPCをWindowsでやっていますが(iOSやるときはMac Book Airをリモートでつないでる)、実機デバッグが楽なのはUWP>AndroidiOSなので、どうしてもiOSの動作確認が最後になってしまい、個人的にそこで詰まるケースが結構多いです。普段使っているスマホiPhoneなので、ちゃんとiPhoneでも使えるようにしたいところではあるのですが。

あと、今回はなるべくシンプルに作りたかったのでコードはほとんどコードビハインドに書いてみました。ただ、これが失敗だったのか、結局コードビハインドでいろいろ頑張ることになってしまい、シンプルというよりは見通しが悪くなってしまったような印象。
拡張性とかも考えて、ちゃんとMVVMでやったほうがむしろ「シンプル」できれいになるのかも、と思いました。のちほど、Prismを使って再実装してみようかとも思います。

JAZUG 7周年総会で「社内でIoT&AIな農業系チャットボット開発講座をやってみて」というテーマで登壇してきました

はじめに

先日、9/9(土)に行われたJAZUG 7周年総会にて、50分のセッションで登壇させていただきました。
そしてこのたび、そのときのセッション動画が公開されましたので、少し間が空いてしまいましたが、簡単に内容の紹介と振り返りを書いておきたいと思います。

イベントの概要

JAZUG 7周年総会のイベントページ(connpass)はこちらです。 jazug.connpass.com

Microsoft AzureのユーザーコミュニティであるJAZUG(Japan Azure User Group)の7周年を記念したイベントということもあり、たくさんの方が参加されたイベントでした。

仕事以外のコミュニティで話をするのは4月にやったJXUG関連の初心者LT以来2度目でしたが、今回は参加人数も多く、Azureを長く使われている方も多くいる中での登壇でしかもロングの本編登壇だったので、しっかり準備しなければいけないと思い、内容については前日ぎりぎりまで悩みながら資料を作りました。

発表内容と資料・動画

タイトルは「社内でIoT&AIな農業系チャットボット開発講座をやってみて」
内容はタイトルどおり、社内でIoTやAIに絡めた講座をやった際に使ったAzureのサービスに関することを中心にお話しさせていただきました。

資料はSlideShareに上げています。

www.slideshare.net

動画はこちらです。 crash.academy

社内でやったこの講座では、植物に取り付けたIoTデバイスRaspberry Piなどで作成)が湿度データと植物を撮影した写真データを定期的にAzureに送り、そのデータを使ってLINE Botを介してユーザーと会話をする、というシステムを作りました。
f:id:himanago:20170924221756p:plain

使ったAzureのサービスも多種多様で、以下のような構成です。 f:id:himanago:20170924221802p:plain

その他、細かい内容は資料・動画を見ていただくとして、ここからは登壇にあたってどういうことを考えて準備したかや、実際に登壇してみての感想・反省などの振り返りをメインに書いていきます。

登壇のきっかけ

7月のJAZUGの勉強会に参加させていただいた際、登壇面白そうだなと思ったので、アンケートで「登壇希望」と書きました。

ちょうど昨年11月~今年3月にかけて社内でAzureのサービスを多く利用したIoT講座をやっていたりしたので、それがネタになるだろうと思ってのことです。
その後、JAZUGの方から連絡があり、予定するセッション内容などをお伝えしたところ、8月のお盆休み中に、JAZUGの7周年総会でセッションを担当できないか、と正式な依頼をいただきました。

7周年という大きな節目のイベント、しかもあまり準備日数がなさそうということで、少し迷いましたが、むしろこんな舞台でセッションをさせていただける機会はめったにないと思い、ありがたく登壇させていただくことにしました。

準備(ターゲット設定)

まず、今回登壇させていただくにあたり、たくさんの方が参加されるイベントなので、きっちり話す内容を検討しないといけないと思い、以下のようなターゲット設定を行いました。

ターゲット①:Azure歴の長い方

7周年総会ということで、当然、Azure歴の長い方が多く参加されると思いました。そういった方にも飽きずに聞いていただけるよう、以下のような話を盛り込むことにしました。

  1. Azure以外のネタ
  2. Azureのよく使うサービスだけど意外と知らないかもしれないこと
  3. Azure歴の短い人間から見たAzureの進化

1.は、今回UI部分にLINE Botを使ったので、Azure FunctionsやCognitive Servicesを使ってLINE Botを作れるという点、またLINE Beaconなどを紹介することで満たせました。
特にCognitive Services(LUIS、Computer Vision API)がLINE Botと相性が良く、事例として紹介したいと思っていたところでした。

2.については、Cognitive ServicesのComputer Vision APIのサムネイル生成機能あたりがそれに該当します。Computer Vision APIは画像の分析が注目されるAPIなので、自分自身もサムネイル生成もできるというのは少し意外だと感じたためです。
この機能は今回の教材システムのなかでも大きな役目を担っていますが、スライドでいうとこの部分。 f:id:himanago:20171001212606p:plain

少しここで補足しておくと、LINE Botでは、画像をユーザーに送る際には、いくつか注意点があります。
まず、画像の「URL」を教えてあげる必要があるという点です(公開されている必要がある)。今回送りたい画像(植物の写真)はBlob Storageに入れていましたが、Blob StorageではそれぞれのデータにURLが割り当てられるので、LINE Botで送信可能でした。

もうひとつは、LINE Botで画像を送る場合はオリジナルの画像URLとは別にサムネイルの画像URLも必要だという点です。
オリジナル画像のみでも画像の送信は可能ですが、サムネイルをセットで渡してあげないと、ユーザー側のLINEのトーク画面に、重いオリジナル画像がそのまま表示されてしまいます。これでは、画像のサイズが大きいときにトーク画面になかなか画像が表示されずストレスになってしまいます。そこで、トーク画面にはサイズの軽いサムネイルを出し、それをタップすると高解像度のオリジナル画像が表示される、というようにしてあげる必要があるのです。

今回、それはComputer Vision APIを組み合わせることで実装することができました。
LINEから「写真みせて」とメッセージを受け取ったら、Blob Storageに格納してある写真のオリジナルデータのURLをComputer Vision APIに渡し、サムネイル生成を依頼します。するとサムネイルの画像データを送り返してくるので、それをBlob Storageのサムネイル用コンテナー(オリジナル画像の入っているコンテナーとは別)に入れ、そのサムネイル画像のURLと、オリジナル画像のURLをセットでLINEのMessaging APIに送り返します。そうすることで、Messaging APIからユーザーのLINEに画像がサムネイルつきで送られることになります。Blob Storage+Computer Vision APIのサムネイル生成がどんぴしゃでこの実装に使えて、当時はちょっと感動しました。

3.は、Functions開発の進化についてです。
講座当時にはなかったVisual Studio 2017との連携について触れ、「わずか7ヶ月で進歩が感じられるほどAzureは常に進化している」ということを(確認的に)伝え、Visual StudioでのFunctions開発をデモすることにしました。

ターゲット②:今回触れるAzureサービスにそこまでなじみがない方

イベントに参加されるのはAzureに詳しい方だけとは当然限らないので、今回話に出てくるAzureのサービスをあまり知らない/使ったことがない人に向けても、「そのサービスの概要」「使う利点」などを伝えられればと思いました。
こういうコミュニティの参加者の前提知識をどこに設定するかというのは難しい問題だと思いますが、あまり基本的な話をしすぎると最初のターゲットと設定した人が退屈に感じてしまうので、今回は話の中で登場するAzureのサービスについてはひとこと説明をスライドに載せておくことで対応することにしました。

反省

「つかみ」は大事

今回、スライドのデザインはJAZUGでよく使われているものをカスタマイズさせていただいたのですが、これに関連して、 「農業関連の話なのでスライドの下のほうに草生えてます」と最初のつかみで言うのを忘れてしまいました…。

ウケるにしてもウケないにしても、こういう「つかみ」のネタをやって空気感の確認をやっておくと、話す側としてもペースをつかみやすくなるんですよね。 今回はそれができなかったこともあって、いつも通りな話し方のペースをつかむまで、少し時間がかかってしまったような気がします。 なので、前半ちょっと余計なことを話したりその場で思いついたことを話したりしてしまい、後半で少し走り気味になってしまったように思っていましたが、動画をあらためて見てもそこまで走っている感じはしなかったので、許容範囲に収まってよかったかな、と思います。
とはいえ、やはり最初に会場の雰囲気と自分の立ち位置を把握するため、しっかりと「つかみ」を大事にしないといけないな、と改めて感じました。

小道具が活かせなかった

「Spotlight」という、画面の一部をスポットライトで照らしたように見せる機能を持ったクリッカーバイスを持ってきていたのですが、あまり使う必要のあるスライドが少なく、一度だけ申し訳程度に使っただけで終わってしまいました。結局ずっとPCの前で操作をしていましたし、クリッカーとしてのも出番もなく。。
Spotlightはこれです。 www.logicool.co.jp

7周年総会の最後のLT大会で同じ道具を使っている方がいらっしゃったのですが、ものすごくウケていたのでちょっと悔しかったです^^;
次回はスライドをSpotlight使用前提で作って、さらに自分自身もPCから離れて動きながら話してみたいなと思いました。

資料作成がぎりぎり

社内で講師をするときもそうですが、本当にいつも登壇前日まで資料作成をやっている気がします…。余裕をもって準備できていないとリハーサルなども入念にできないので、早め早めの準備は心がけたいところです。
リハーサルをもっとしっかりできていれば上記2点のような反省点も出なかったはずなので、次回はきっちりと余裕をもっていきたいと思います。

振り返りブログが遅い

冒頭に「動画が公開されたので」というふうに書きましたが、それは嘘です…。
本当は登壇直後に公開したかったのですが、内容も振り返りも…といろいろ考えていたら文章のまとまりがなくなり、収拾がつかないまま時間だけが過ぎてしまいました。
動画が公開されたので、このタイミングでこそ出さなければ…!と、なんとか振り返り中心にまとめて公開までこぎつけた次第です。
次回もし登壇をした場合はもっと早く出せるよう、事前に準備しておきたいなと思います。

LINE Messaging APIに関して若干の補足

登壇時、しゃべり損ねてしまったのですが(動画を見直して気づきました)、今回使ったLINE Botの機能は「Reply API」という、ユーザーからのメッセージに応答(返信)するためのAPIで、LINE Botにはもうひとつ、「Push API」というBotからユーザーにメッセージを送るためのAPIが用意されています。
Reply APIはフリープランでも使えますが、Push APIではプロ版(月額料金がかかる)か開発者版(友だち数に制限がある)でしか使えません。そのため、今回はReply APIのみでシステムを構築しています。

まとめ

ということで、社内の講座のためにAzureを触り始め、もうすぐ8か月くらいになります。IoTやAIなんていうと大それた言葉ですが、Azure(とLINE Bot)でうまく実現でき、その可能性に大きな魅力を感じました。今回、JAZUGでの登壇を通して、その魅力を多くの方に伝えられていたらいいなと思います。
現在Azureを業務でも使えるところに取り入れられたらと思い、いろいろ試しているのでこれからも勉強していこうと思います。引き続きJAZUGには参加させていただこうと思いますので、どうぞよろしくお願いいたしますm(_ _)m

「BLOB」の読み方は?

はじめに

普段「BLOB」のことを「ビーロブ」と発音していたのですが、最近「ブロブ」と発音する人が多いことを知りました。
人前でしゃべることも多いので、正しい(もしくは多数派の)発音を知っておきたいと思い、ちょっと調べてみました。

BLOBとは何か

Binary Large OBjectの略。データベースなどでバイナリデータを格納する際のデータ型のことを言います。
似た概念に、大きなテキストデータを格納するためのデータ型であるCLOB(Character Large OBject)というものもあります。
参考:バイナリ・ラージ・オブジェクト - Wikipedia

発音の派閥

「ビーロブ」派

Oracleのドキュメント
BLOBの読み方が「ビーロブ」であると明記されています。
当然、CLOBは「シーロブ」となっています。
用語集

自分が最初に出会ったRDBMSOracleだったため、その影響で「ビーロブ」と発音するようになったようです。

ブロブ」派

・IT用語辞典 e-Words
読み方に「ビーエルオービー/ブロブ」と書かれています。
BLOBとは - IT用語辞典

しかし、CLOBのほうは「シーロブ/シーエルオービー」と書かれています。統一されていない…。
CLOB(キャラクタラージオブジェクト)とは - IT用語辞典

英語圏では?

Channel9の動画(Azure Blob Storageの話が出てくるもの)で英語スピーカーが何と言ってるかを聞いてみました。

channel9.msdn.com
channel9.msdn.com

めっちゃ「ブラーブ」と言ってますね…。
日本語っぽい発音にしたら「ブロブ」、ということになりますね。

結論

英語圏の人もそうですし日本でもそう言う人が多いので、「ブロブ」と発音したいと思います。
ただ、Oracleの文脈ではドキュメントの読み方に従い、今後も「ビーロブ」と言っていこうと思います。

Grails3.3でrest-apiプロファイルを試す(Spring Securityでの認証つき)

はじめに

長いこと業務でGrailsを使っているので、Grailsの動向が気になっています。

Grailsとは、GroovyというJavaを強力に進化させたスクリプト言語を用いたフルスタックなWebアプリケーションフレームワークです。

Ruby on Railsのように「CoC(設定より規約)」「DRY(Don’t Repeat Yourself)」を信条とし、煩わしい作業や無駄な記述を一切排除し素早くWebアプリを開発できる、高い生産性が魅力です。

そんなGrailsですが、つい最近、3.3が正式リリースになりました。

社内の認証プロキシがどうにも突破できずうまく開発できないので現在の業務では2.5を使って開発していますが、ビルドシステムがGradleになり大きく構成の変わった3.xはずっと気になっていました。今回3.3が出たのでちょっと試してみたところ、プロキシ突破できたので、3.x系に乗り換えようか悩み中。。
※プロキシ突破できた話はメモとしてQiitaに書きました

qiita.com

とりあえず、いま作ってるアプリはそのまま2.5で作って、そのあと徐々に3.x系に移行していけばいいかな、と思っています。

さて、今回はGrails 3の出番に備え、リリースしたての3.3を使って、認証機能つきのWeb APIを作ってみたいと思います。 業務で作っているシステムは複数システムを連携ありで作る想定なので、Web APIで連携しやすくすることが重要になるため、ここの検証をしておきます。今回作成したものはGithubに上げています。

github.com

以下、Grailsの簡単な説明を交えつつのやったことの流れです。

「rest-api」プロファイルでプロジェクトを作成

Grailsではコマンドを用いて開発を進めます。アプリの新規作成は「create-app」コマンドで行いますが、Grails 3になると、作りたいアプリの構成ごとに「プロファイル」が用意されており、作成時に指定することができます。プロファイルを指定せずに「create-app」コマンドでアプリを新規作成すると、デフォルトのプロファイルである「web」プロファイルが用いられ、通常のWebアプリケーションのプロジェクトが作成されます。
一方、

grails create-app api-sample --profile=rest-api

とオプションで「rest-api」プロファイルを指定すると、RESTで叩けるWeb APIを作るための雛形プロジェクトを作ってくれます。

ドメインクラスとScaffold

GrailsMVCモデルのフレームワークです。
モデル(DBのテーブルに相当し、Grailsではドメインクラスと呼ばれます)、ビュー(WebアプリではHTMLのレンダリングを司る)、コントローラー(双方の仲立ち)により構成されます。

Grailsでは「設定より規約」により、モデル、ビュー、コントローラーのファイル名を規約通りにつけることで、それぞれが連動し複雑な設定なしでWebアプリとして動いてくれます。具体的には、たとえば書籍を管理するWebアプリの場合「Book.groovy」というモデル(ドメインクラス)、「BookController.groovy」というコントローラークラスを作り、「BookController.groovy」には「index」というpublicなメソッドを作ります(このメソッドを「アクション」と呼びます)。そして、アクションと同じ名前のビュー「index.gsp」というファイルをディレクトリ名がドメイン名と同じであるディレクトリに作ります。つまり、以下のような構成です。

domain
└ Book.groovy
controllers
└ BookController.groovy
views
└ book
   └ index.gsp

この状態でWebアプリを起動し、「http://xxxx/sampleapp/book/index」というURLにアクセスすると、URLに書かれたとおりのコントローラークラスのアクションメソッドが実行され、同様にビューが表示されます。このあたりの仕組みはRuby on Railsと同じですね。

そして、このコントローラーとビューは、ドメインクラスをもとに自動生成することが可能で、それが「Scaffold」です。

ドメインクラスを

package rest.s2.sample

class Book {
    // 属性
    String name
    String author

    // 制約
    static constraints = {
        name blank: false
        author blank: false
    }
}

と定義します。そして、 ‘’‘ grails generate-all rest.s2.sample.Book ’‘’ と実行すると、ドメインクラスに定義した通りの「Book」データをDBと連携し一覧表示/登録/編集/削除ができるようなコントローラーとビューを生成してくれます。これがScaffoldです。自動生成されたコードは実際にそのまま本番で使用することは少ないと思いますが、基本的な動作の書き方の参考にしたり、カスタマイズして流用したりすることができ、開発効率向上に一役買ってくれます。

rest-api の場合の Scaffold

さて、こんなScaffoldですが、「rest-api」プロファイルで作成されたプロジェクトで行うと、ちゃんとRESTで呼べるものが自動生成されます。 特に重要なのはビューで、通常は拡張子が「.gsp」であるGSPファイル(Groovy Server Pages。HTMLをレンダリングするためのファイル)が生成されますが、rest-apiの場合に生成されるのが「.gson」のGSONファイルです。これは、その名の通りGSPのJSON版で、リクエストに対しJSONのレスポンスを返すための情報を記載するファイルです。

起動と動作確認

Scaffoldまでできたら、「run-app」コマンドでアプリを起動します。 APサーバー、DBがあらかじめ含まれているのですぐ動作確認できます(DRYな特徴のひとつ)。 今回はrest-apiなのでブラウザではなくコマンドから確認します。Windows環境なので、PowerShellInvoke-RestMethodを使ってみます。この方法だと、送信するデータをスクリプトで組み立てられるため、RESTの動作確認におすすめです。

RESTということなので、今回、URLにアクションは含めません。UrlMappingsという設定ファイルで、HTTPメソッドとアクションメソッドの対応が定義されており、Grailsのデフォルトの規約を上書きしています。rest-apiでは、規約より設定、ということでしょうか。

package grails3.rest.api.s2.sample

class UrlMappings {

    static mappings = {
        delete "/$controller/$id(.$format)?"(action:"delete")
        get "/$controller(.$format)?"(action:"index")
        get "/$controller/$id(.$format)?"(action:"show")
        post "/$controller(.$format)?"(action:"save")
        put "/$controller/$id(.$format)?"(action:"update")
        patch "/$controller/$id(.$format)?"(action:"patch")

        "/"(controller: 'application', action:'index')
        "500"(view: '/error')
        "404"(view: '/notFound')
    }[f:id:himanago:20170803094920p:plain]
}

実行結果は以下のようになりました。まず登録。

PS C:\> Invoke-RestMethod -Uri "http://localhost:8080/book" -Method POST -Body @{name="Sample Book";author="Sample Author"}

id name        author
-- ----        ------
 1 Sample Book Sample Author

そして一覧表示です。

PS C:\> Invoke-RestMethod -Uri "http://localhost:8080/book" -Method GET

id name        author
-- ----        ------
 1 Sample Book Sample Author

たったこれだけで、RESTなWeb APIを作成することができました。

認証機能をつけるプラグイン

では、次にこのAPIに認証機能を付けてみたいと思います。Grailsには、認証機能をつけるための「Spring Security」がプラグインとして用意されています。プラグインは、Grailsの大きな利点のひとつで、さまざまな機能を追加したり、またよく使う機能を自前のプラグインとして外部化することもできます。

それでは、Spring Securityプラグインを使って認証機能を追加します。 rest-apiの場合、使うのはSpring Security CoreSpring Security RESTの2つ。

プラグインは、build.gradle内のdependenciesに以下の行を追加するだけで導入できます。

    compile 'org.grails.plugins:spring-security-core:3.2.0.M1'
    compile "org.grails.plugins:spring-security-rest:2.0.0.M2"

ちなみに認証の流れはSpring Security RESTのUser guideに載っているこの図がわかりやすいです。 rest

s2-quickstart

Spring Security Coreには、コマンド一発で認証に必要なユーザーとロールのドメインを作ってくれる「s2-quickstart」コマンドが用意されています。

grails s2-quickstart rest.s2.sample.auth User Role

次に、できあがったクラス(User、Roleとその関連を管理するUserRole)に対してgenerate-allでコントローラー/ビューを生成しておきます。

grails generate-all rest.s2.sample.auth.User
grails generate-all rest.s2.sample.auth.Role
grails generate-all rest.s2.sample.auth.UserRole

権限設定

Spring Securityでは、コントローラーのクラス単位/アクション単位でSecuredアノテーションを付けることできめ細かく権限制御をすることができます。ログイン有無や、Userに紐付くRoleごとの制御ができます。今回は、BookControllerのアクションを「ROLE_ADMIN」というRoleを持つUserでログインしていないと実行できないようにしてみます。

記述はクラス名の上に「@Secured(‘ROLE_ADMIN’)」をつけるだけです。もちろん、importは必要になります(この参照がうまくいかない場合はGradleプロジェクトのリフレッシュを行います)。

package rest.s2.sample

import grails.plugin.springsecurity.annotation.Secured
...

@Secured('ROLE_ADMIN')
class BookController {

}

各種設定

初期ユーザー登録

init/Bootstrap.groovyに、起動時処理として初期ユーザーの登録処理を書いておきます。今回は、このユーザーで認証を行います。

package grails3.rest.api.s2.sample

import rest.s2.sample.auth.Role
import rest.s2.sample.auth.User
import rest.s2.sample.auth.UserRole

class BootStrap {

    def init = { servletContext ->
        def user = new User(username: 'admin', password: 'admin')
        user.save(flush: true)
        def role = new Role(authority: 'ROLE_ADMIN')
        role.save(flush: true)
        new UserRole(user: user, role: role).save(flush: true)
    }
    def destroy = {
    }
}

設定ファイルの変更

今回はconf/application.ymlに設定を書きます。

grails:
    ....

    plugin.springsecurity:
        userLookup.userDomainClassName: 'rest.s2.sample.auth.User'
        userLookup.authorityJoinClassName: 'rest.s2.sample.auth.UserRole'
        authority.className: 'rest.s2.sample.auth.Role'
        filterChain.chainMap:
            #Stateless chain
            - {pattern: '/**', filters: 'JOINED_FILTERS,-anonymousAuthenticationFilter,-exceptionTranslationFilter,-authenticationProcessingFilter,-securityContextPersistenceFilter,-rememberMeAuthenticationFilter'}
        rest.token:
                validation:
                    useBearerToken: false
                    headerName: 'X-Auth-Token'

application.groovyにあるSpring Security関連の設定はすべてコメントアウトします。

実行結果を確認

では、実行確認です。run-appして、Invoke-RestMethodコマンドを実行していきます。

認証情報のない状態で、先ほどと同じようにbookの登録を行おうとすると、403エラーになります。

PS C:\> Invoke-RestMethod -Uri "http://localhost:8080/book" -Method POST -Body @{name="Sample Book";author="Sample Author"}
Invoke-RestMethod : リモート サーバーがエラーを返しました: (403) 使用不可能
発生場所 行:1 文字:1
+ Invoke-RestMethod -Uri "http://localhost:8080/book" -Method POST -Bod ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-RestMethod]、WebException
    + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand

なのでまずは認証をし、アクセストークンを得ます。実行したスクリプト

$user = @{
    username='admin'
    password='admin'
}
$json = $user | ConvertTo-Json
$response = Invoke-RestMethod 'http://localhost:8080/api/login' -Method POST -Body $json -ContentType 'application/json'
echo $response.access_token

<実行結果>

PS C:\> $user = @{
>>     username='admin'
>>     password='admin'
>> }
PS C:\> $json = $user | ConvertTo-Json
PS C:\> $response = Invoke-RestMethod 'http://localhost:8080/api/login' -Method POST -Body $json -ContentType 'application/json'
PS C:\> echo $response.access_token
eyJhbGciOiJIUzI1NiJ9.eyJwcmluY2lwYWwiOiJINHNJQUFBQUFBQUFBSlZTdlVcL2JRQlJcL1RoTlJnVlNnRWtnZFlBRTI1RWgwek1SbjFjb05xR2tXa0VBWCsrRWVu
5cGFKRXFkZTNJZjlJdVwvUU5RTzNSbDd0cDNodURBZ25xVFwvZTduMzlmenhSVlVqSWJuc1daY0dEOFZXY3lsYjFMTlpXd3d6RFMzSFQ4enFDTzBPZUpGRG16U0JLNlBW
4cXdvbTQrcG02d0JEVzJ0cldGSTZ2bUhjMXl6QkU2VVBcL1Z2dVVHbThJMUJRZTk5S01MSU5reXdNVlNadFhjbjFkc28xUnRzd1Vjd0NGUjY2MFZSSU55Z3RaOElNUTBk
sUTVHZ3ZqMTJZenkwVzFnYllXd09PVUdVUHU3aVZwV0dmZDNUdWJraEljd1hzb3QxT1BEblczNEtDKzRcL0ZYbFJDVW1pdHA1cHN5VVJIZjUwNmMrTHN6WnpcLzdYN3ZO
wMHYrXC8rbmMyTDlrSUwwMFBXQzFpdG5aS2J5WUw1clVhbmZQbGw2OVA1MVllZFI2VHNFQnZcL3Y0XC81NVp2bU9xc3FTWmxtVmczdGlHaFB5dTZaeUZjZUpoOXNvZU0z
waWlsdldTZ3o2dGpENlpqTlkzMXRlZVwvMnk3bDRyTEVxNEpOVW5lV2kzTFQ5UXRLdmVuNDhcLytuT1wvaU9FVlZJNlp5SkE2bnloQTlTeHBvVDY5T0o4WitcL3k3bHlj
SQXdBQSIsInN1YiI6ImFkbWluIiwicm9sZXMiOlsiUk9MRV9BRE1JTiJdLCJleHAiOjE1MDE2NTI5ODcsImlhdCI6MTUwMTY0OTM4N30.P2n42_q6WSNY9weZad23aRSP

次にBookの登録です。

$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.Add("X-Auth-Token", $response.access_token)
Invoke-RestMethod -Uri "http://localhost:8080/book" -Method POST -Body @{name="Sample Book";author="Sample Author"} -Headers $headers

<実行結果>

PS C:\> $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
PS C:\> $headers.Add("X-Auth-Token", $response.access_token)
PS C:\> Invoke-RestMethod -Uri "http://localhost:8080/book" -Method POST -Body @{name="Sample Book";author="Sample Author"} -Headers $headers

id name        author
-- ----        ------
 1 Sample Book Sample Author

ちなみに、アクセストークンが誤っていた場合は、401エラーになります。

ということで無事、認証つきでRESTのAPIをたたくことができました。
これだけ手軽に認証機能つきのWeb APIが作れるようになり、Grailsはとても便利に進化していると思います。

画面ロックのショートカットをタスクバーにピン留めする(Windows 10)

はじめに

こんにちは。
今回は画面ロックをタスクバーからかんたんに行えるようにする小技を紹介します。

経緯

(本題とは関係ないので読み飛ばして結構です) 最近会社のメインPCが変わりました。念願のWindows10です。
会社では長らく開発用(Win8.1)とメイン(Win7)の2台体制で仕事していましたが、今回メイン機が7→10と入れ替わったかたち。

この2台はInput Directorというツールでマウス・キーボードを共有しています。

Input Directorは、PCを同一ネットワーク上にある別のPCから操作するためのツールです。
マスター(操作する側のPC)のマウスとキーボードで、スレーブ側(操作される側のPC)を操作できる優れものです。

なのですが、1点、不便なのが画面ロックです。
画面ロックは通常、Winキー+Lやファンクションキーの組み合わせなどで行えますが、マウス・キーボード共有している状態でスレーブ側を画面ロックしようとしても、マスター側がロックされるだけで、スレーブ側はとロックされません。
つまり、キーボード共有した状態で画面ロックのショートカットキーを入力すると、スレーブ側ではなくマスター側がロックされてしまうので、スレーブ側をロックするには別の手段を用いる必要が出てきます。
そこで使ったのが、今回の「タスクバーに画面ロックショートカットをピン留めする」という方法です。

画面ロックをコマンドで行う

画面ロックをタスクバーから実行するために、まず考えたのはバッチ化する、ということでした。
バッチ化するために、コマンドから画面ロックを実行する方法を調べたところ、下記の記事がヒットしたので、使わせていただきました。

www.projectgroup.info

ここに書かれている通り、「rundll32.exe user32.dll,LockWorkStation」と書いたファイルを作成し、拡張子を「bat」すればOKです。
このファイルをダブルクリックなどで実行すると、しっかり画面ロックされます。 (今回は「ロック.bat」というファイルにしました)

ショートカット化する

バッチファイルは、そのままではタスクバーにピン留めできません。 f:id:himanago:20170729230928p:plain

そこで、作ったバッチファイルをショートカット化します。
バッチファイルを右クリック→ショートカットの作成、とするとショートカットができます(ショートカットの名前は「ロック」にしておきます)。
これだけではまだタスクバーにピン留めできないので、これをコマンドプロンプト経由で呼ぶよう、変更します。 今回はバッチファイルは「C:\bat」のフォルダに保存しているので、作成したショートカットのリンク先は「C:\bat\ロック.bat」となっています。
これを、「C:\bat\ロック.bat」→「C:\Windows\System32\cmd.exe /C C:\bat\ロック.bat」と変更することで、コマンドプロンプト経由で呼ばれるようになります。
f:id:himanago:20170729231714p:plain:w300

こうすると、バッチファイルはタスクバーにピン留めできるようになります。

f:id:himanago:20170729234003p:plain:w400

コマンドプロンプトのアイコンだと味気ないので、アイコンも変更します。
アイコンはフラットデザインでよさげなものを選択。今回はこちらの「PNG」「背景なし」を使わせていただきました。

flat-icon-design.com

これをicoに変換すると、ショートカットアイコンとして使えるようになります。
今回はオンラインで変換できる下記のサイトを使用。

app.tree-web.net

アイコンの準備ができたら、再びショートカットのプロパティを開き、アイコンを変更し、タスクバーにピン留めしなおします。
これで、ちょっとおしゃれな画面ロックショートカットをタスクバーにピン留めすることができました。

f:id:himanago:20170730011353p:plain:w200

もう一工夫

これだけでもいいのですが、この状態でピン留めしたショートカットから画面ロックをしようとすると、コマンドプロンプトの画面が一瞬出てしまいます。
そこで、コマンドプロンプトが出ない方法を調べると、VBScriptで記述すればよい、ということがわかりました。

d.hatena.ne.jp

拡張子を変更し、「ロック.vbs」というファイル名にします。 中身は以下のコードに変更します。

Dim oShell
Set oShell = WScript.CreateObject ("WSCript.shell")
oShell.run "rundll32.exe user32.dll,LockWorkStation",0
Set oShell = Nothing

ショートカットのプロパティのリンク先も、VBScriptになったので以下のように実行するexeを変更します。

C:\Windows\System32\wscript.exe //B C:\bat\ロック.vbs

最後に、ショートカットをタスクバーにピン留めしなおします。
これで、ワンクリックできれいにロックがかかるようになりました!
快適な2PC生活が送れそうです。

Oracle Database XE を Linux 32bit環境にインストールする

はじめに

どうにも最近業務が忙しく、なかなかブログが書けません。 下書きばかりがたまっていきます…。

さて、ここ数日久しぶりにOracleをいじっていたのですが、社内の研修用環境として、32bitのLinuxVM(CentOS 6.9)にOracleデータベースの無償版であるExpress Edition(以下XE)を入れようとしたら見事にハマりました。

11g以降のバージョンで、OracleデータベースのXEではLinuxは64bit版のみの提供で、32bit版は提供されなくなってしまっていたのです(Windows向けは両方あり)。
かといって、10gの32bit用XEも公開が停止されており、公式ではどうにもならない模様。
今回は要件的に32bitのLinux+XEという組み合わせがMUSTだったので、なんとか粘って解決策を見つけました。

この組み合わせが必要になった場面、実は今回で2度目なので(1度目はあきらめた)、需要あるかも?ということで備忘も兼ねて書いておきます。

基本的に32bitのLinux向けXEは入手不可

11g以降はXEについて32bitのLinux向けが提供されていないということで、検索してみるとやはりそれを求める人の質問が結構ヒットします。
しかし、それらの結論はXEはあきらめて上位エディション入れましょう、など「不可能」という回答ばかり。
海外の質問サイトで、ここにリンクが残っていますよ、というような回答をみつけたりもしたのですが、すでにリンク切れ。
あきらめずに根気よく探すと、以下の記述を発見…!

「この中にあるよ」

unix.stackexchange.com ここには、 http://oss.oracle.com/debian/ に10gXEが含まれていると書かれていました。

探ってみると、こちらから10gXE(32bit)のパッケージがダウンロードができました! https://oss.oracle.com/debian/dists/unstable/non-free/binary-i386/ (この中の「oracle-xe-universal_10.2.0.1-1.1_i386.deb」というやつです)

しかし、形式がdebなので、今回使うCentOSではインストールできません。
そこで、こちらの記事を参考に、rpmに変換してインストールしました。

dotnsf.blog.jp

ということで、無事Linux32bitの環境にOracleXEを入れることができました。

MCP試験(70-483:C#によるプログラミング)を受けてきました

はじめに

先月末、Microsoftの認定資格であるMCPの、「C#によるプログラミング(70-483)」を受けてきました。 今回は受けるにあたっての準備と実際の試験についてなどを書き残しておこうと思います。

動機

この試験をなぜ受けたか、というと理由は3つです。

1. スキルの証明

かれこれ7年半くらい前から業務でも頻繁に使っているC#について、スキルを持っているということを対外的に証明したかったというのがひとつ。

同じく継続的に使っているJavaについてもOracleの認定資格をとったりしているので、C#でもそれをやっておきたいと思ったため、時間があれば前から受けたいと思ってました。

2. 講師登壇の説得力

上記と少しかぶるのですが、今年度会社でC#関連の講座を担当するので、講師として説得力をもてるようにしたかったことです。
講師プロフィールに関連資格が書いてあると、とりあえず「この人ちゃんと知ってるのかな」って思いますよね。
もちろん、資格だけあればいいかというとそういうわけでないのはもちろんですが。

3. de:code MCP割引

これがいちばん大きかったです。 f:id:himanago:20170521210618p:plainde:code 2017 リーフレットより)

de:codeのMCP割引は税抜\56,000で、早期割引(税抜\68,000)よりも安いんです。
なので、まずはこのタイミングで試験を受けてしまって、だめだったら早期割引の値段で申し込むことにしました。
早期割引が4/28までだったので、駆け込みで4/24に受けました。

試験の概要

試験で出題されるC#のバージョンは、試験の概要に「Visual Studio 2012」と記載されているので、C# 5.0」となります。
async/awaitあたりまでが試験範囲に含まれることになります。

準備

この試験には日本語の対策書籍(MCP教科書 Windows 10(試験番号:70-697)などのいわゆる赤本)がないので、英語の書籍ですが、Exam Refというものを使いました。
MCP対策書籍としては定番もののようです。

https://www.amazon.co.jp/dp/0735676828/www.amazon.co.jp

すべてを読み込むことはできませんでしたが、練習問題が各パートごとに数問載っているので、どんな問題が出るのか知る上で重宝しました。

章立ては試験に沿っていて、

  • Manage Program Flow(プログラムフローの管理)
  • Create and Use Types(型の作成と使用)
  • Debug Applications and Implement Security(アプリケーションのデバッグとセキュリティの実装)
  • Implement Data Access(データ アクセスの実装)

と、試験範囲と同じ構成になっています(全384ページ)。
全部をじっくり読み込むのは相当骨が折れそうですし、ある程度C#経験があれば文法的な部分はおおよそ問題ないので、ほとんどは流し読みで、気になったところはじっくり読む、という使い方をしました。

個人的には、セキュリティの実装あたりは普段そこまで意識していなかったところなので、ちゃんと勉強する必要があるなと思いました。

しかし結局、4月は業務が忙しく、ろくに対策をする時間もとれず、「もう落ちてもいいや!」と当たって砕けろな覚悟で申し込みました。
すべてはde:codeのため。受験を先延ばしにして万が一そこで落ちて早期割引でも申し込めなかったり、さらにはチケット完売なんてなったりしてしまったら目も当てられませんから。

試験当日

夕方18:30からの予約だったので定時ダッシュして秋葉原の試験センターへ。

ひととおり手続きを終え、予約時間になったらPCの前へ。
案内には試験時間が150分と書いてあったのに実際には120分だったり、試験開始前に、「試験はどうでしたか?」的なアンケート答えさせられたりと、若干混乱させられましたが、試験が始まりました。

形式はほとんどが多肢選択式。
プログラムの虫食い部分に当てはまるコードや、正しい実行結果を選択します。

なかには、複数の選択肢から複数の虫食い部分にドラッグ&ドロップして解答するものも。
この形式でswitchのcase, default, breakの穴埋めが出たときは「ばかにしてるのか!」とも思いましたが、LINQのクエリ形式やasync/awaitまわりのキーワードを埋める問題も出たので、そこは楽しく解いていくことができました。

案の定、対策の不十分だった「アプリケーションのデバッグとセキュリティの実装」まわりの問題には苦戦しました。

またなかには、翻訳が微妙な問題(ひとつだけ「あれ?」と思ったものがあった)や、解答の選択肢のソースコードから単語間のスペースが消えて詰まってしまっているものなどがありました。

こういうときは、試験問題の表示を英語に切り替えることをおすすめします(試験問題の機能でできる)。
英語に切り替えると、ソースコードの空白がちゃんと表示され、読みやすくなりました。

また、自分の知識と照らし合わせて問題と選択肢が「何か変だな」と思ったら、英語の方を読んでみると、誤訳とまではいかなくても、ニュアンスの微妙な違いから英語のほうが本来の意味を理解できることがあります(そんなに英語が得意でなくても)。

こういう対応ができたのも、Exam Refを見ておいたことで英語の問題文への抵抗感がそこまでなくなっていたからかな、と思います。

結果

そんなこんなで試験を終え、結果は無事合格でした。合格点は700点とのことだったので、7割合格でしょうか。
点数はぎりぎりで、やはりというか、「アプリケーションのデバッグとセキュリティの実装」のところがやたら低かったです。
そこ以外でなんとか点数をとれていたのと、(全部でなくとも)Exam Refを見ておいたことが今回の勝因かと思います。

まとめ

Exam Refの確認はしておいたほうがいいなと思いました。
試験本番で英語に切り替えることも戦略として一つの手なので、その対策の意味でも必須だと感じました。

とはいえ、基本的なことがしっかりわかっているかが結果に大きく関わってくるので、やはり普段からの経験がものをいうのは事実でしょう。
実際、今回は試験対策が功を奏したというよりは地力で踏みとどまった感じですし。

このあとの認定パスとしては70-357:Developing Mobile Appsを受けてMCSA: Universal Windows Platformを目指すか、
70-486:Developing ASP.NET MVC Web Applicationsを受けてMCSA: Web Applicationsを目指すかの2択になりそうです。
UWPもASP.NETもきちんと勉強したいなと思っていた技術なので、どちらに挑戦しようかまだ悩み中です。今年度中には、ぜひ挑戦したいと思います。

そして明後日からはついにde:code 2017!
前夜祭のDay0と今回初となる「Hack Days」のDay3,4も含めて5日連続参加します。最新テクノロジに触れられる貴重な機会、楽しんできたいと思います。

以上です。