Deep Insider の Tutor コーナー
>>  Deep Insider は本サイトからスピンオフした姉妹サイトです。よろしく! 
特集:Azure上での高度な定期処理の実現

特集:Azure上での高度な定期処理の実現

スケジュールされたジョブ機能とAzure Webサイトを組み合わせた定期処理の実装

2013年5月10日

新機能の「スケジュールされたジョブ機能」を使えば、設定された時刻に定期的に自動実行される処理を、Microsoft Azure上で容易に実現できる。その実現方法を解説する。

勇 大地
  • このエントリーをはてなブックマークに追加

はじめに

 2012年末に発表されたAzureモバイル・サービス(以降、モバイル・サービス)の機能強化として、Microsoft Azure(旧称:Windows Azure)上に定期処理を実行可能な「スケジュールされたジョブ機能」が追加された。この機能を利用することで、バックグラウンド処理を行うジョブを作成し、設定された時刻に定期的な処理を自動実行させることが容易となった。これにより、例えば定期的なデータ整理・取得(古い情報や重複の削除、TweetやRSSの収集、画像や動画の処理)、定期的なプッシュ通知など、これまではコンピューティング・サービスで個別に作り込む必要があった処理を、モバイル・サービスの機能でも実現可能になっている。

 本稿では最初に、Azure上で定期処理を実現する複数の手法についてのメリットとデメリットについてまとめる。次に、その実現手法の1つである「スケジュールされたジョブ」機能とAzure Webサイト(以降、Webサイト)の機能を組み合わせて、Azure上で高度な定期処理を実現する方法を説明する。なお、プログラミング言語はC#とJavaScriptを用いる。

スケジュールされたジョブ機能の位置付けと概要

 まず、Azure上で定期処理を実現する方法について紹介する。

 Azure上における定期処理は、モバイル・サービスの「スケジュールされたジョブ機能」以外にも、Azureインフラストラクチャ・サービス(以降、IaaS)を利用して従来どおりに、Windowsスケジューラを利用したり、Linuxでcronデーモンを利用したりしても実現可能だ。また、Azureクラウド・サービスを利用してWorkerロールで独自に定期処理の実行機能を実装する方法も考えられる。

 これらのメリットとデメリットについて、以下の表にまとめた。

サービスメリットデメリット
IaaSを利用する(cron、またはスケジューラ) ・OS機能を利用して定期処理が実行できる ・常にコンピュータ・リソースが必要となる
Workerロールを利用してスケジューリング処理を作り込む

・.NET Frameworkを利用した細かな処理が実装できる

・定期時間ごとに実行判断する処理を独自に作成する必要がある
・常にコンピュータ・リソースが必要となる

スケジュールされたジョブ機能を利用する

・Azureの機能を利用して定期処理が実行でき、課金されない*1

・Azureの管理ポータルで、直接、JavaScriptコードを記載する必要がある
・サードパーティ製のライブラリは利用できない

Azure上で定期処理を実現する複数の手法についてのメリットとデメリット

*1現在、モバイル・サービスはPreview版であるため課金されないが、今後は課金される可能性がある。

 詳しくは後述するが、スケジュールされたジョブ機能では、定期処理を実行するスケジュールの設定や、管理ポータル上でのスクリプトの記載が可能であり、さらに管理ポータル越しに実行ログを閲覧することも可能だ。

 一方で、スクリプトは管理ポータル上で直接、JavaScriptコードを記述するため、Visual StudioのIntelliSenseによるコード補完などのコーディング支援機能は利用できない。また、1つのJavaScriptファイルで全ての処理を記載する必要があるため、サードパーティが作成したJavaScriptライブラリを利用できない*2といった欠点が存在する。

スケジュールされたジョブ機能とWebサイトを利用した定期処理の仕組み

 スケジュールされたジョブ機能だけで複雑な処理を実現するのは困難であるため、本稿ではスケジュールされたジョブ機能とWebサイトを組み合わせて、次の図のような仕組みにすることで、Azure上における高度な定期処理を実現する。なお、Webサイトは無料枠の範囲内であればコンピュータ・リソースに対する課金が発生しないので、この仕組みは基本的に無料で実現できる。

サンプル・アプリケーションの概要

今回は、スケジュールされたジョブ機能を利用する。この機能により、Webサイト上に構築したASP.NET Web APIが定期的に呼び出される。そのWeb API内でYahoo!ニュースWeb APIを用いて「アベノミクス」というキーワードが含まれたニュースを取得し、そのニュースを特定のメール・アドレス宛に送付する機能を実装する。なお、このメール送信には、「SendGrid」と呼ばれるサードパーティ製のサービスを(後述する無償利用枠の範囲内で)利用している。
また、今回利用したサンプル・アプリケーションは、GitHub上に「WebAPIBatch」として公開している。本稿を読み進めながら、実際にサンプル・アプリケーションを動作させて内容を確認してほしい。

 今回のサンプル・アプリケーションを実際に試したい場合で、Azureサブスクリプションに未登録の場合は、まずは事前にサブスクリプションを有効化する必要がある(その手順は「MSDN: Windows Azureサブスクリプション申し込みStep by Step」を参照してほしい)。

事前準備1: Azure Webサイトの作成

 まずは、Yahoo!ニュースのデータ取得やメール送信処理を行うASP.NET Web APIアプリケーションの実行環境であるWebサイトをAzure上に作成する。これには、管理ポータルの下部にあるバーから[新規]-[コンピューティング]-[WEB サイト]-[簡易作成]を選択し、(次の画面のように)独自の[URL](この例では「atmarkitbatch」)と[地域](この例では「East Asia」)を指定して[WEB サイトの作成]をクリックし、新規のWebサイトを作成する。

Azure Webサイトの新規作成

事前準備2: Yahoo! JAPANへの登録

 次に、Yahoo! JAPANデベロッパーネットワークにアクセスし、当該サイトに記載された手順に従い、Yahoo! JAPAN IDを取得する。Yahoo! JAPAN IDを取得した後、Yahoo!ニュースWeb APIを利用する[アプリケーション名](この例では「atmarkitsample」)と[サイトURL](この例では「http://atmarkitsample.azurewebsites.net/」)を[サーバーサイド]形式で登録して[アプリケーションID]を取得する(次の画面を参照)。

Yahoo! JAPANデベロッパーネットワークへのアプリケーションの登録

 今回のサンプル・アプリケーションではYahoo!ニュースWeb APIのトピックAPIを利用するため、「トピックAPIのリファレンス」を一読してほしい。

事前準備3: SendGridの利用登録

 次に、SendGridのサービスの利用登録方法を簡単に紹介するが、すでにSendGridのサービスを利用している場合は本節を読み飛ばしても問題ない。再び管理ポータルで[新規]-[ストア]を選択し、以下の画面から「SendGrid」のサービスを選択する。

SendGridのサービスの利用登録手順

右下の(→)ボタンをクリック

次へ
SendGridのサービスの利用登録手順

[Freeプラン]を選択すれば、2万5千通までのメールは無料で送付可能となる。今回は「東アジア」地域を選択し、再度、右下の(→)ボタンをクリック。次のページで画面の情報に従ってSendGridのサービスを[購入]する

SendGridのサービスの利用登録手順

 SendGridのサービスに登録後、管理ポータルの左サイドのメニューの[アドオン]を押下し、(右サイドで)[SendGrid]をクリックする。SendGridの[ダッシュボード]から、画面下部の[出力値]ボタンをクリックすることで、以下の画面が表示される。

SendGridのSMTPサーバー名、ユーザー名、パスワードの確認

 上の画面で、SendGridを利用するためのSMTPサーバー名、ユーザー名、パスワードを確認する。なお、SendGrid登録までの詳細な手順については、「SendGridを使用したAzure Mobile Servicesからのメール送信 - Virtuoso - Shotaro Suzuki's Blog - MSDN Blogs」を参照すること。

 以上で準備は整った。それではいよいよ、Webサイト上に定期処理を実装していく。

定期処理の実装

ASP.NET Web APIプロジェクトの新規作成

 作成したWebサイトにデプロイするASP.NET Web APIアプリケーションを作成する。

 これには、まずVisual Studio 2012を起動し、メニューバーから[ファイル]-[新しいプロジェクト]を選択して、[新しいプロジェクト]ダイアログを起動する。あとは、以下の画面の手順に従って新規プロジェクトを作成する(.NET Frameworkのバージョンは「4.5」を選択すること)。

ASP.NET Web APIプロジェクトの新規作成

左サイドのツリー表示から[テンプレート]-[Visual C#]-[Web ]を選択し、右サイドのリスト表示から[ASP.NET MVC4 Web アプリケーション]テンプレートを選択し、任意の名前(この例では「WebAPIBatch」)を指定して[OK]ボタンをクリックすると、次の画面が表示される

次へ
ASP.NET Web APIプロジェクトの新規作成

[Web API]プロジェクト・テンプレートを選択して[OK]ボタンをクリックすれば、実際にプロジェクトが作成される

ASP.NET Web APIプロジェクトの新規作成

RazorEngineライブラリの追加

 ASP.NET Web APIプロジェクトの新規作成が完了したら、送信メールの本文を作成するために利用するテンプレート・エンジンであるRazorEngineライブラリをプロジェクトに追加する。

 具体的には、[ソリューション エクスプローラー]上のプロジェクト項目を右クリックし、(表示されるコンテキスト・メニューの)[NuGet パッケージ管理]を実行する。これにより、以下の画面の[NuGet パッケージの管理]ダイアログが起動するので、右上の検索欄に「Razor」を入力して「RazorEngine」のライブラリを検索する。

RazorEngineライブラリの追加([NuGet パッケージの管理]ダイアログ)

 上の画面で「RazorEngine」を選択すると、[インストール]ボタンが表示されるのでこれをクリックし、ASP.NET Web APIプロジェクトに対してRazorEngineライブラリをインストールする。

 以上でASP.NET Web APIプロジェクトの環境構築が完了した。次に、ASP.NET Web APIアプリケーションの処理を実装する。

ASP.NET Web APIのPostメソッドの実装

 次のコードは、POSTリクエストを受け付けるコントローラーのメソッドの実装例である(なお、ひな型のコードが自動生成されているので、中身を消して下記のコードに書き換える)。

C#
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Threading.Tasks;
using System.Web.Http;
 
public class ValuesController : ApiController
{
  // POST api/values
  public async Task Post([FromBody]WebAPIArgs args)
  {
    // (1)Yahoo!ニュースの結果を絞り込むために利用するキーワード
    var keyword = ConfigurationManager.AppSettings["YahooAPI:Keyword"];
 
    // (2)非同期形式でYahoo!ニュースWeb APIを呼び、キーワードで絞った検索結果を取得
    var results = await GetNewsAsync(keyword);
 
    // (3)メールを送信
    SendMails(args, keyword, results);
  }
}
POSTリクエストを受け付けるコントローラーのメソッドの実装(ValuesController.cs)

ASP.NET Web APIのコントローラーを実装するには、このコード例のようにApiControllerクラス(System.Web.Http名前空間)を継承する。

 上記のコードのPostメソッドの内容についてはコメントや下記の説明を参考にしてほしい。一部のクラスやメソッドの実装は後述する。現段階ではまだビルドできないので注意されたい。

 (1)ConfigurationManager.AppSettingsプロパティを利用して、Web.Config設定ファイル(の「YahooAPI:Keyword」キー)からYahoo!ニュースWeb APIから検索するニュースのキーワードを取得している。Web.configファイルの内容は後述する。

 (2)外部サービス呼び出し時の応答性を向上させるため、非同期処理として定義したGetNewsAsyncメソッドを呼び出す。非同期処理については、「@IT/Insider.NET: 特集:.NET開発者のための非同期入門:フリーズしないアプリケーションの作り方」を参照すること。

 (3)Yahoo!ニュースのデータ取得後、SendMailsメソッドを呼び出してニュースをメールで送付する。

(1)Web.configファイルへの追記

 以下に、今回利用したWeb.Configファイルにおける設定内容の一部を抜粋したので、設定内容を環境(前述の準備段階に取得したYahoo!のアプリケーションIDやSendGridのSMTPサーバー名など)に合わせて適宜変更すること。

ASP.NET
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  ……省略……
  <appSettings>
    ……省略……
    <add key="YahooAPI:Appid" value="<取得したアプリケーションID>" />
    <add key="YahooAPI:Keyword" value="アベノミクス" />
    <add key="YahooAPI:Pickupcategory" value="domestic" />
    <add key="Mail:SMTPServerAddress" value="<SendGridのサーバー>" />
    <add key="Mail:SMTPServerUsername" value="<SendGridのユーザー名>" />
    <add key="Mail:SMTPServerPassword" value="<SendGridのパスワード>" />
  </appSettings>
……省略……
Web.Configファイルにおける設定内容の一部抜粋

(2)ニュースの取得処理

 次に、Yahoo! JAPANデベロッパーネットワークで取得したアプリケーションIDを利用してニュースを取得する処理(=前述のGetNewsAsyncメソッド)を実装する。

 具体的には、下記のコードをValuesControllerクラス内に追記すればよい。

C#
using System.Linq;
using System.Web.Http;
……省略……
 
// 利用するYahoo APIのURL
const string YAHOO_API_STRING = @"http://news.yahooapis.jp/NewsWebService/V2/topics?appid={0}&pickupcategory={1}";
 
private async Task<IEnumerable<YahooNewsResult>> GetNewsAsync(string keyword)
{
  // (a)Yahoo!ニュースの情報を取得
  var res = await new HttpClient().GetAsync(string.Format(YAHOO_API_STRING,
    ConfigurationManager.AppSettings["YahooAPI:Appid"],
    ConfigurationManager.AppSettings["YahooAPI:Pickupcategory"]));
 
  // (b)XMLデータを利用し、キーワードで該当するニュースを抽出
  return (await res.Content.ReadAsAsync<YahooNewsResultSet>())
    .Where(_ => _.Title.Contains(keyword) || _.Overview.Contains(keyword));
}
ニュースを取得するGetNewsAsync非同期メソッドの実装(ValuesController.cs)

 (a)上記のコードでは、HttpClientクラス(System.Net.Http名前空間)のGetAsyncメソッドを利用してYahoo!ニュースWeb APIからHttpResponseMessage(System.Net.Http名前空間)形式のデータ(=変数「res」)を取得している。

 (b)そのデータのContentプロパティから取得したHttpContentオブジェクト(System.Net.Http名前空間)のReadAsAsync<YahooNewsResultSet>メソッドを呼び出して、YahooNewsResultSet型(詳細後述)のデータを取得し、キーワードを利用してニュースの結果を絞り込んで抽出している。

 YahooNewsResultSet型のデータを取得する際には、次のコード(ValuesControllerクラスの下に追記)に示すように、CollectionDataContract属性DataContract属性DataMember属性(いずれもSystem.Runtime.Serialization名前空間)を利用して、Yahoo!ニュースWeb APIから受け取ったXMLデータを、YahooNewsResult型のデータ・オブジェクトへ逆シリアル化している(シリアル化と逆シリアル化処理についての詳しい方法は「DOBON.NET: DataContractSerializerを使って、オブジェクトのXMLシリアル化、逆シリアル化を行う」を参照)。

C#
using System.Runtime.Serialization;
……省略……
 
[DataContract(Name = "Result", Namespace = "urn:yahoo:jp:news")]
public class YahooNewsResult
{
  [DataMember(Name = "CreateTime", Order = 2)]
  public DateTime CreateTime { get; set; }
 
  [DataMember(Name = "Title", Order = 6)]
  public string Title { get; set; }
 
  [DataMember(Name = "Overview", Order = 10)]
  public string Overview { get; set; }
 
  [DataMember(Name = "SmartphoneUrl", Order = 21)]
  public Uri SmartphoneUrl { get; set; }
}
 
[CollectionDataContract(Name = "ResultSet", Namespace = "urn:yahoo:jp:news")]
public class YahooNewsResultSet : List<YahooNewsResult>
{ }
YahooNewsResultSet型の定義(ValuesController.cs)

Yahoo!ニュースWeb APIから受け取ったXMLデータを、YahooNewsResult型のデータ・オブジェクトへ逆シリアル化している。このコードを実装するには、System.Runtime.Serializationアセンブリへの参照を追加する必要がある。

(3)メールの送信処理

 最後に、取得したニュースのデータをSendGridサービスを利用してメール送付する処理(=前述のSendMailsメソッド)を実装する。

 具体的には、下記のコードをValuesControllerクラス内に追記すればよい。

C#
using System.Net.Mail;
using RazorEngine;
……省略……
 
private void SendMails(WebAPIArgs args, string keyword, IEnumerable<YahooNewsResult> results)
{
  using (var sc = new SmtpClient())
  {
    // SMTPサーバーを指定
    sc.Host = ConfigurationManager.AppSettings["Mail:SMTPServerAddress"];
    sc.Credentials =
      new System.Net.NetworkCredential(
        ConfigurationManager.AppSettings["Mail:SMTPServerUsername"],
        ConfigurationManager.AppSettings["Mail:SMTPServerPassword"]);
 
    // 送付先のメール・アドレスにニュースを送付する
    foreach (var toAddress in args.ToMailAddresses)
    {
      // 送付用のメール・メッセージの作成
      using (var msg = CreateMailMessage(args, keyword, results, toAddress))
      {
        sc.Send(msg);
      }
    }
  }
}
 
private MailMessage CreateMailMessage(WebAPIArgs args, string keyword, IEnumerable<YahooNewsResult> results, string toAddress)
{
  var msg = new MailMessage();
 
  // 送信者の設定
  msg.From = new MailAddress(args.FromMailAddress, args.FromMailName);
 
  // 宛先の設定
  msg.To.Add(new MailAddress(toAddress));
 
  // 件名の設定
  msg.Subject = keyword + "に関する記事です";
 
  // 本文の設定(RazorEngineテンプレート・エンジンを利用)
  msg.Body = results.Any() ? Razor.Parse(keyword + @"に関する記事のURLは以下になります
@foreach( var result in Model ){
@:・ @result.SmartphoneUrl
}", results) : keyword + @"に関する記事は見つかりませんでした";
  return msg;
}
メールを送信するSendMailsメソッドの実装(ValuesController.cs)

 上記のコードのようにValueControllerクラス内に実装したSendMailsメソッドでは、SmtpClientクラス(System.Net.Mail)を利用した単純なメールの送付処理を記載している。

 また、同じくValueControllerクラスに追記したCreateMailMessageメソッド側では、メール送付用のメッセージを作成しており、この際にASP.NET MVCの標準的なテンプレート・エンジンとしても利用されている「RazorEngine」をメール用のテンプレート・エンジンとして利用している。

ASP.NET Web APIアプリケーションの完成とWebサイトへの配置

 最後に、ここまでのコードで何度も登場しているWebAPIArgs型(次のコードを参照)を、ValueControllerクラスの下に追記しよう。このWebAPIArgs型は、Web APIの呼び出し元からPOSTされるJSONデータの内容を表す。

C#
public class WebAPIArgs
{
  public string FromMailName { get; set; }
  public string FromMailAddress { get; set; }
  public IList<string> ToMailAddresses { get; set; }
}
WebAPIArgs型の定義(ValuesController.cs)

 以上で、ASP.NET Web APIアプリケーションの実装が完了したため、それをWebサイト上にデプロイする。アプリケーションの配置方法については、「@IT/Insider.NET: 特集:Windows Azure Webサイト入門 新規サイトが素早く立ち上げられるWindows Azure Webサイト」を参考にしてほしい。ここでは説明を割愛するが、Web配置ツールを利用して作成したアプリケーションをWebサイトへデプロイする。

Azureモバイル・サービスの新規作成

 次に、スケジュールされたジョブ機能の処理を実装する。

 まず、管理ポータルから[新規]-[コンピューティング]-[モバイル サービス]-[作成]を実行する。次の画面のページが表示されるので、[URL]に任意のURL前(この例では「disami-mobile-service」)を指定し、[データベース]は任意の値(この例では「既存の SQL データベースを使用します」)を選択し、[地域]を「East Asia」にして、(→)ボタンをクリックする。次のページでSQLデータベースの設定をして、最後に(レ)(=完了)ボタンをクリックすると、新規のモバイル・サービスが作成される。モバイル・サービス作成方法の詳細については、「MSDN: Windows Azure Mobile Services for iOSチュートリアル」を別途参照すること。

モバイル・サービスの作成

モバイル・サービスで「スケジュールされたジョブ」の新規作成

 作成したモバイル・サービスの項目をクリックし、さらに[スケジューラ]タブを選択する。これにより次の画面が表示されるので、[スケジュールされたジョブを作成します]リンクをクリックして新しいジョブを作成する。

スケジュールされたジョブの新規作成

[スケジュールされたジョブを作成します]リンクを押下すると、次の画面の[新しいジョブの作成]ページに切り替わる

次へ
スケジュールされたジョブの新規作成

[ジョブ名]を「atmarkitjob」(=後述のコード内容と一致させる必要があるために、今回は必ずこの名前にする)とし、[スケジュール]を今回は「要求時」として、最後に右下にある(レ)(=完了)ボタンをクリックすると、新規ジョブが作成される

スケジュールされたジョブの新規作成

ジョブ処理の実装

 管理ポータル上で、先ほど作成した新しいジョブの項目をクリックし、さらに[スクリプト]タブを選択してスクリプトの編集画面を表示する。その編集画面を使って、以下のスクリプトを記載する。

JavaScript
function atmarkitjob() {
  callwebsite("http://<Webサイト名>.azurewebsites.net/api/values/");
}
 
function callwebsite(url) {
  console.info("Executing job: call Yahoo API website");
 
  var req = require('request');
  req.post({
    headers: { 'Content-Type': 'application/json' },
    url: url,
    body: JSON.stringify({
      FromMailName: "勇大地",
      FromMailAddress: "xxxxxxxx@hotmail.com",
      ToMailAddresses: [
        "yyyyyyyy@hotmail.com",
        "zzzzzzzz@hotmail.com"
      ]
    })
  },
  function (error, result, body) {
    if (body) {
      console.error("Job execute failed");
      console.error(body);
    } else {
      console.info("Job executed successfully");
    }
  });
}
ジョブに記載するスクリプト(JavaScript)

require関数は、モジュールを呼び出すためのもので

 このスクリプトによって、ジョブが実行された際、上記のcallwebsite関数の引数として指定されたURLのWebサイト(今回は「http://atmarkitbatch.azurewebsites.net/api/values/」)に、JSON形式でのPOSTリクエストを送付する。JSON形式でPOSTリクエストのパラメーターを送付するため、(HTTPの)Content-Typeヘッダーを「application/json」としている。また、ASP.NET Web API側の処理で例外が発生した場合は、レスポンスのbody部分にエラー・メッセージが格納されるため、body値の有無を確認してログの出力レベルを決定している。

 スクリプトの記載が完了したので、(次の画面のように)管理ポータルの下部にある[保存]ボタンを押下してスクリプトを保存する。

スクリプトの保存

作成したサンプル・アプリケーションの実行

 以上ですべての実装が完了した。最後に、ジョブを実行して、定期処理が正常に動作するかを確認してみよう。

 再び管理ポータル上で「atmarkitjob」ジョブの[スクリプト]タブを選択し、その下部にある[一度だけ実行]ボタンを押下してスクリプトを実行する。

 正常にスクリプトが実行されれば、キーワードで抽出されたニュースが登録されたメール・アドレスに送付される。次の画面は、そのメール内容の例である。

スクリプト実行成功時のメール内容の例

 また、管理ポータルからモバイル・サービスの[ログ]タブを選択することで、アプリケーションの実行結果であるログ出力を確認できる。特にエラーが発生していない場合は、以下の画面のようなログ・メッセージが表示される。

スクリプト実行成功時のログ・メッセージの表示例

まとめ

 本稿では、「スケジュールされたジョブ機能」「Webサイト」「ASP.NET Web API」を組み合わせてMicrosoft Azureで(JavaScriptコードだけでは実現できない)高度な定期処理を実装する方法を紹介した。さらに、サードパーティ製のメール・サービスであるSendGridや、テンプレート・エンジンであるRazorEngine、非同期処理についての利用方法についても簡単に記載した。また、スケジュールされたジョブ機能はWebサイトだけでなく、既存のオンプレミス・システムとの連携も可能である。

 今回作成したサンプル・アプリケーションで利用した機能は多いため、この中から利用可能な情報があればぜひ現場でも利用してほしい。

サイトからのお知らせ

Twitterでつぶやこう!