月曜日, 8月 04, 2008

【JavaScript】 prototype.jsを抜粋して作ったとても小さなAJAXライブラリ このエントリーを含むはてなブックマーク


最近、モッサリ感たっぷりなAJAXライブラリを使うのがとても苦痛になってきたので、必要なところ以外バッサリ切り捨ててみた。このデモ(ここをクリック)
を見てもらうと分かるとは思うが、XMLHttp通信、JSON読込、Object.Extend、bind()、Event.observe()/stopObserving()、Element.show()/hide()、キャッシュ付$()、styleをセットできるBuilder.node()、配列のeach()、any()、$H()、$H(x).toXML()、$H(x).toJSON()などが使える。
これでなんと526行しかない。(ちょっと増えました8/9)
とっても短いAJAXライブラリ extractive.js
 
 デモは左にテキストボックスが現れるので、何か入力してEnterを押すと、右にBOXが描画される。Deleteキーで描画OFF、Insertキーで描画ON、Homeキーで連続表示(each())、ESCキーでAJAX読込、TABキーでJSON読込が実行される。


<extractiveソース>

/**
* extractive.js
*                 
* Functions taken from Prototype library, Log4js
* didn't want to require for just few functions.
* More info at {@link http://prototype.conio.net/}
*            
*/

var Builder = {

   initialize: function() {
   },
   camelize: function(name) {
   var oStringList = name.split('-');
   if (oStringList.length == 1) return oStringList[0];

   var camelizedString = name.indexOf('-') == 0
     ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1)
    : oStringList[0];

   for (var i = 1, len = oStringList.length; i < len; i++) {
    var s = oStringList[i];
    camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
   }

   return camelizedString;
  },
   node: function(elementname, param, text) {
     var element = document.createElement(elementname);
      if (param.id) element.id = param.id;
      if (param.className) element.className = param.className;
      if (param.style) {
        for (name in param.style) {
           element.style[this.camelize(name)] = param.style[name];
         }
      }
      if (text) {
         var value = document.createTextNode(text);
         element.appendChild(value);
      }
      if (param.src) element.src = param.src;
      return element;
   },
   // http://bmky.net/text/note/javascript-css.html
   //
   getActiveStyle: function ( element, property, pseudo ) {
       if( element.currentStyle ) { //IE or Opera
         if( property.indexOf( "-" ) != -1 ) property = property.camelize( );
         return element.currentStyle[ property ];
       }
       else if( getComputedStyle ) { //Mozilla or Opera
         if( property.indexOf( "-" ) == -1 ) property = property.deCamelize( );
         return getComputedStyle( element, pseudo ).getPropertyValue( property );
       }
        return "";
   }
}

var Prototype = {
 Version: '1.4.0',
 ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)',

 emptyFunction: function() {},
 K: function(x) {return x}
}


var Class = {
 create: function() {
  return function() {
   this.initialize.apply(this, arguments);
  }
 }
}

function $(e){return $[e]||($[e]=(document.getElementById(e)||e))}

var Element = {

 hide: function(argument) {
   var element = $(argument);
   element.style.display = 'none';
 },

 show: function(argument) {
   var element = $(argument);
   element.style.display = '';
 }
}

/**
* Object extention (OO) methods if no prototype avaliable.
*
* @private
* @ignore
*/
if(!Object.prototype.extend) {
 Object.extend = function(destination, source) {
  for (property in source) {
  destination[property] = source[property];
  }
  return destination;
 };

 Object.prototype.extend = function(object) {
  return Object.extend.apply(this, [this, object]);
 };
}

if(!Function.prototype.bind) {
Function.prototype.bind = function(object) {
var __method = this;
return function() {
return __method.apply(object, arguments);
};
};
}
var $break  = new Object();
var $continue = new Object();

var Enumerable = {
 
 K: function(x) {return x},
 
 _each: function(iterator) {
  for (var i = 0; i < this.length; i++)
   iterator(this[i]);
 },

 each: function(iterator) {
  var index = 0;
  try {
   this._each(function(value) {
    try {
     iterator(value, index++);
    } catch (e) {
     if (e != $continue) throw e;
    }
   });
  } catch (e) {
   if (e != $break) throw e;
  }
 },
 all: function(iterator) {
  var result = true;
  this.each(function(value, index) {
   result = result && !!(iterator || Prototype.K)(value, index);
   if (!result) throw $break;
  });
  return result;
 },

 any: function(iterator) {
  var result = true;
  this.each(function(value, index) {
   if (result = !!(iterator || Prototype.K)(value, index))
    throw $break;
  });
  return result;
 },

 collect: function(iterator) {
  var results = [];
  this.each(function(value, index) {
   results.push(iterator(value, index));
  });
  return results;
 },

 detect: function (iterator) {
  var result;
  this.each(function(value, index) {
   if (iterator(value, index)) {
    result = value;
    throw $break;
   }
  });
  return result;
 }, 
 findAll: function(iterator) {
  var results = [];
  this.each(function(value, index) {
   if (iterator(value, index))
    results.push(value);
  });
  return results;
 },

 grep: function(pattern, iterator) {
  var results = [];
  this.each(function(value, index) {
   var stringValue = value.toString();
   if (stringValue.match(pattern))
    results.push((iterator || Prototype.K)(value, index));
  })
  return results;
 },

 max: function(iterator) {
  var result;
  this.each(function(value, index) {
   value = (iterator || Prototype.K)(value, index);
   if (value >= (result || value))
    result = value;
  });
  return result;
 },
 min: function(iterator) {
  var result;
  this.each(function(value, index) {
   value = (iterator || Prototype.K)(value, index);
   if (value <= (result || value))
    result = value;
  });
  return result;
 },
 pluck: function(property) {
  var results = [];
  this.each(function(value, index) {
   results.push(value[property]);
  });
  return results;
 }, 
 sortBy: function(iterator) {
  return this.collect(function(value, index) {
   return {value: value, criteria: iterator(value, index)};
  }).sort(function(left, right) {
   var a = left.criteria, b = right.criteria;
   return a < b ? -1 : a > b ? 1 : 0;
  }).pluck('value');
 },
 toArray: function() {
  return this.collect(Prototype.K);
 },
 include: function(object) {
  var found = false;
  this.each(function(value) {
   if (value == object) {
    found = true;
    throw $break;
   }
  });
  return found;
 },
 inject: function(memo, iterator) {
  this.each(function(value, index) {
   memo = iterator(memo, value, index);
  });
  return memo;
 }
}

Object.inspect = function(object) {
  try {
   if (object === undefined) return 'undefined';
   if (object === null) return 'null';
   return object.inspect ? object.inspect() : object.toString();
  } catch (e) {
   if (e instanceof RangeError) return '...';
   throw e;
  }
}

Object.extend(Enumerable, {
 map:   Enumerable.collect,
 find:  Enumerable.detect,
 select: Enumerable.findAll,
 member: Enumerable.include,
 entries: Enumerable.toArray
});

Object.extend(Array.prototype, Enumerable);



var Hash = {
 _each: function(iterator) {
  for (key in this) {
   var value = this[key];
   if (typeof value == 'function') continue;

   var pair = [key, value];
   pair.key = key;
   pair.value = value;
   iterator(pair);
  }
 },

 keys: function() {
  return this.pluck('key');
 },

 values: function() {
  return this.pluck('value');
 },

 merge: function(hash) {
  return $H(hash).inject($H(this), function(mergedHash, pair) {
   mergedHash[pair.key] = pair.value;
   return mergedHash;
  });
 },

 toQueryString: function() {
  return this.map(function(pair) {
   return pair.map(encodeURIComponent).join('=');
  }).join('&');
 },

 inspect: function() {
  return '#<Hash:{' + this.map(function(pair) {
   return pair.map(Object.inspect).join(': ');
  }).join(', ') + '}>';
 },
 toXML: function() {
   return this.map(function(pair) {
    return '<'+pair.key+'>'+ (
     typeof pair.value == 'string' ? pair.value : $H(pair.value).toXML()
    ) +'</'+pair.key+'>';
  }).join('');
 },
 toJSON: function() {
  return '{' + this.map(function(pair) {
    if ((typeof pair.value == 'undefined')||(typeof pair.value == 'unknown')) return pair.key+': ""';
   return pair.key+': '+ (
    (typeof pair.value == 'string')||(typeof pair.value == 'number') ? '"'+pair.value+'"' : $H(pair.value).toJSON()
   );

  }).join(', ') + '}';
 }
}

function $H(object) {
 var hash = Object.extend({}, object || {});
 Object.extend(hash, Enumerable);
 Object.extend(hash, Hash);
 return hash;
}

  /**
   * Attatch an observer function to an elements event browser independent.
   *
   * @param element element to attach event
   * @param name name of event
   * @param observer observer method to be called
   * @private
   */

var Event = {

 KEY_BACKSPACE: 8,
 KEY_TAB:    9,
 KEY_RETURN:  13,
 KEY_ESC:   27,
 KEY_LEFT:   37,
 KEY_UP:    38,
 KEY_RIGHT:  39,
 KEY_DOWN:   40,
 KEY_DELETE:  46,

 element: function(event) {
  return event.target || event.srcElement;
 },

 isLeftClick: function(event) {
  return (((event.which) && (event.which == 1)) ||
      ((event.button) && (event.button == 1)));
 },

 pointerX: function(event) {
  return event.pageX || (event.clientX +
   (document.documentElement.scrollLeft || document.body.scrollLeft));
 },

 pointerY: function(event) {
  return event.pageY || (event.clientY +
   (document.documentElement.scrollTop || document.body.scrollTop));
 },

 stop: function(event) {
  if (event.preventDefault) {
   event.preventDefault();
   event.stopPropagation();
  } else {
   event.returnValue = false;
   event.cancelBubble = true;
  }
 },

 // find the first node with the given tagName, starting from the
 // node the event was triggered on; traverses the DOM upwards
 findElement: function(event, tagName) {
  var element = Event.element(event);
  while (element.parentNode && (!element.tagName ||
    (element.tagName.toUpperCase() != tagName.toUpperCase())))
   element = element.parentNode;
  return element;
 },

 observers: false,

 _observeAndCache: function(element, name, observer, useCapture) {
  if (!this.observers) this.observers = [];
  if (element.addEventListener) {
   this.observers.push([element, name, observer, useCapture]);
   element.addEventListener(name, observer, useCapture);
  } else if (element.attachEvent) {
   this.observers.push([element, name, observer, useCapture]);
   element.attachEvent('on' + name, observer);
  }
 },

 unloadCache: function() {
  if (!Event.observers) return;
  for (var i = 0; i < Event.observers.length; i++) {
   Event.stopObserving.apply(this, Event.observers[i]);
   Event.observers[i][0] = null;
  }
  Event.observers = false;
 },

  observe: function (element, name, observer) {
  if (element.addEventListener) { //DOM event model
   element.addEventListener(name, observer, false);
   } else if (element.attachEvent) { //M$ event model
   element.attachEvent('on' + name, observer);
   }
 },
  stopObserving: function(element, name, observer, useCapture) {
    if (element.removeEventListener) {
     element.removeEventListener(name, observer, useCapture);
    } else if (element.detachEvent) {
     element.detachEvent('on' + name, observer);
    }
  }
}

var Requester = {

 createXmlHttp : function() {
 
  var x = null;
  if (window.XMLHttpRequest) { // IE7,Firefox, Safari
   x = new XMLHttpRequest();
   return x;
  }
  try {
   x = new ActiveXObject("Msxml2.XMLHTTP"); // IE6
  }
  catch(e) {
   try {
    x = new ActiveXObject("Microsoft.XMLHTTP"); // IE5
   }
   catch(e) {
    x = null;
   }
  }
  return x;
 },
 /**
  * Load a JS-script dynamically.
  * @param {String} src
  */
 
 loadjson : function(src) {

  var documentScripts = document.getElementsByTagName("script");
 
  for (index = 0; index < documentScripts.length; ++index)
  {
   var documentScript = documentScripts[index];
   if (documentScript.src == src) {
    return false;
   }
  }
  
  var script = document.createElement('script');
  script.type = 'text/javascript';
  script.src = src;
  document.getElementsByTagName('head')[0].appendChild(script);
  return true;
 }

}
/* prevent memory leaks in IE */
Event.observe(window, 'unload', Event.unloadCache, false);
var Position = {
 // set to true if needed, warning: firefox performance problems
 // NOT neeeded for page scrolling, only if draggable contained in
 // scrollable elements
 includeScrollOffsets: false,

 // must be called before calling withinIncludingScrolloffset, every time the
 // page is scrolled
 prepare: function() {
  this.deltaX = window.pageXOffset
        || document.documentElement.scrollLeft
        || document.body.scrollLeft
        || 0;
  this.deltaY = window.pageYOffset
        || document.documentElement.scrollTop
        || document.body.scrollTop
        || 0;
 },

 realOffset: function(element) {
  var valueT = 0, valueL = 0;
  do {
   valueT += element.scrollTop || 0;
   valueL += element.scrollLeft || 0;
   element = element.parentNode;
  } while (element);
  return [valueL, valueT];
 },

 cumulativeOffset: function(element) {
  var valueT = 0, valueL = 0;
  do {
   valueT += element.offsetTop || 0;
   valueL += element.offsetLeft || 0;
   element = element.offsetParent;
  } while (element);
  return [valueL, valueT];
 }
}


<デモソース>

<html>
<head>
<meta http-equiv="Content-Script-Type" content="text/javascript" />
<style type="text/css">
.content1 {
  height:10;
  position:absolute;
  margin-bottom:5px;
  background:#FFCC99;
  border:1px solid #FF0000;
  padding:1px 1 20px 1;
  font-size:10px;
  width:100px;
  
}
</style>
<script type="text/javascript" src="extractive.js"></script>
<script type="text/javascript">

var initial = new Array();
initial.push("1");
initial.push("2");
initial.push("3");

var Editable = function() {
  
    this.editbox = $('EditBox');
    Event.observe(this.editbox, "keypress", this.onKeyPress.bind(this));
}

Editable.prototype = {

   onKeyPress: function(event) {
   switch(event.keyCode) {
   
    case 9:        // tab
    
    Requester.loadjson("http://localhost:8081/json.txt");

    try {
     alert("test:"+testjson);
    }catch (e) {}
    return;  

    case 13:        // Enter

    var element1 = Builder.node('div', { className : 'content1',style :{ top : '25',left :'300' }});
    element1.innerHTML = $('EditBox').innerHTML.replace(/<\/?[^>]+>/gi, '')
    $('entity').appendChild(element1);

    alert($('entity').innerHTML);

      return;
    case 27:        // ESC (AjaxRequest)

    this.req = Requester.createXmlHttp();
    var url = "http://localhost:8081/";
    this.req.onreadystatechange = this.callback.bind(this);
    this.req.open("GET", url, true);
    this.req.send("");

    return;

    case 45:        // insert
    Element.show('entity');
    return;
   
    case 46:        // delete
     Element.hide('entity');
    return;

    case 36:        // home
    var contentmain = $('entity');
    initial.each(
      function(value,index) { 
        var content = Builder.node('div', { id : 'id'+index, className : 'content1',style :{ top : 100 + index*30 +'px' ,left :'300' } },value );
        contentmain.appendChild(content);
      }.bind(this)      
    );

   }
   
  },
  callback: function () {
    if(this.req.readyState == 4) {  // 読込完了
      if(this.req.status == 200) { // HTTP OK
        alert(this.req.responseText);
      }
      else {
        alert(this.req.status);
      }
    }
  }

  
}
</script>
<title></title>
</head>
<body>

<DIV id="EditBox" style="float:left;width:200;height:500;background:white;border:silver 1px solid;padding:3px" contenteditable>
<BR><BR>
</DIV>

<div id="right" style="float:right;">
<div id="entity"></div>
</div>

<script type="text/javascript">
new Editable();
</script>

</body>
</html>

日曜日, 8月 03, 2008

【お知らせ】 バーチャルテクノロジーは第二ステージへ このエントリーを含むはてなブックマーク


 本日、トップページを更新した。弊社の取り組みと方針

 第一ステージの命題は果たせたし、今の会社の実態に合わなくなってきたので、いつかは更新しなきゃなあとは思っていた。でも、なかなかできなかったのは、SaaSとクラウドのどちらに比重を置くべきかを悩んでいたためだ。これは勝手な思い込みではあるが、SaaSはお客様業務と直結するので、カストマイズ対応には限界がある。一方、クラウドは検索などの機能に限定することで対応可能である。
 では、クラウドで実際に何をするかというと、まずは分散KVSに対応したReflexを使った検索機能で、次に、受注データや在庫データなどを管理できるサービスを提供する。最終的にはECサイトを構築したいと考えている。

 ここまでが第二ステージ。

 ここから第三ステージ(妄想開始)
 ITのパラダイムがシフトして、クラウドに代わるもの(きっとP2P)が登場するだろうと考えている。第三ステージはそこで動くフレームワークとなるだろう。イメージはここに書いているようなものだ。Google技術のオープンソース化がきっかけで、名前はまだないが、SETI@Homeのような並列処理が可能な、自前クラウドが出現してくると思う。そして、「Fuck!あちら側」運動が起きると予想する。これが現実味を帯びるためには、フォルトトレーラントとセキュリティが鍵となるだろう。これは5年後ぐらいの話だ。(妄想終わり)

<第三ステージのReflex>

Reflexは、「P2Pで安全に商取引ができるインフラの提供」を目的としたミドルウェアであるが、なぜP2Pにこだわるかをここでちょっと説明したい。

一極集中から新しいパラダイムが生まれて分散へ流れ、それから、また集中に動く。
これをITにおける輪廻という。 (by IBM 中島DE)

Yahoo やGoogleといった巨大なポータルサイトもあるが、インターネットは、原理的にはIPによる個々のコンピュータによるネットワークである。すなわち、基本的に分散型であり、インターネットの特長を最大限に生かすには、P2Pのような分散型が最適であると考えられる。

今、WEB2.0という新しいパラダイムが生まれて、CGMという個人の情報が重要だと認識された。
CGMを最も効率よく流通させるのは、インターネットの特長を生かしたP2Pであることは、疑いようがない。

また、今後膨大なCGMが発信するようになると、1極集中型が破綻すると私は予想している。ムーアの法則がサチッてきている以上、横に広がる以外ないのだ。


 ちなみに、第一ステージは何だったかというと、オープンソースによる基幹システムの構築とWebサービスの活用である。Reflexの基本的な部品を作ったばかりだったので、それを実際のシステムに入れてみたかった。今ちゃんと動いているので、命題は果たせたことになる。ということで、第一ステージクリアー。(SaaSはいろいろ問題があってできなかった。前記事参照)

<2007年抱負>


今年は、ある会社のネットビジネスを手がける予定だ。Ajax、などWeb2.0バリバリで、また、SaaSのモデルもやりたい。たぶん、その会社のCTOとして参加することになると思うが、正式に決まったらお知らせしたい。

あれだけ去年熱く語っていたReflexはなんだったんだ?と思わないでほしい。このビジネスでも、Relfe CoreやReflex iTextなどは、どんどん活用していくつもりだ。P2P機能は次のフェーズになるとは思うけど、まあ、リアルビジネスが優先されちゃうのは常ということで・・・。(いかんいかん)

金曜日, 8月 01, 2008

【雑記】 妄想族の憂鬱 このエントリーを含むはてなブックマーク


 妄想をしなければ生きていけない人のことを妄想族という。いつも夢みたいなことばかり言う人たちである。バーチャルテクノロジー≒妄想技術である当社の社長も妄想族の一人である。先日、IBMの先輩と話をする機会があった。「IBMにいる頃は、あなたは夢見たいなことばかりいってたけど、独立して変わりましたね。」といってくれたが、実は何も変わっていないことで恐れられているのを知らないだろう?IT業界は社長だらけである。福岡の室見川の橋の下にも社長がいっぱいいる。妄想族と引きこもりは紙一重なので、頑張って社会と繋がっていなければならない。バカにされることが多いかもしれないが、妄想はやめてはならない。妄想が現実になると、ビジョナリーとして一転して尊敬されるようになることもある、という妄想を同じ妄想族で(ある社長)と飲み会で話した。いやあ、妄想族は生きていくだけでも大変なのに社員を養っていくなんて大したものだ。独立すれば自由は得られるが孤独感・不安感に苛まれることになる。それに打ち勝つには妄想があればよい。独立の条件=妄想族といいたいところだが、実際は妄想を抑えきれなくて独立したという方が正しい。
 さて、話が変わって、最近の若者について。一つ目はHadoopを研究している大田さんとその関係者。
YLUG 第88回カーネル読書会 Googleの基盤クローンHadoopについて(このビデオは難しいけど勉強になるのでおススメだ)
 彼のグループ、はてな、それから学生ベンチャーの会社が合同で並列技術(Hadoop)を研究して、一部の機能をはてなの検索に使っているらしいのだが、これはとてもいい時代になったなと思った。はてなは日本のGoogleになろうとしているのかもしれない。梅田さんや近藤さん、Naoyaさんたちは、これまでにない価値観で日本のベンチャーの先駆けとなってくれている。それに優秀な学生がついていっているのは本当に明るい話である。優秀な学生は官僚や大企業に入ってスポイルされてはいけない。やはり、新しい価値を生み出せるところにどんどん挑戦していくべきだろう。それにはやはり夢が必要なんだが、はてなにはそれがあるということかもしれない。ちなみに私は妄想ということで夢を語ることを続けていきたい。(誰だ!笑っているのは・・)
 一方の優秀な学生の記事を読むと、[ルポ]弾氏への応答
 ・・・ちょっとガッカリ。何がガッカリかというと、なんというか、弾さんはたしかに有名人ではあるが、ちょっと失礼かもしれないが、夢の対象や人生の目標ではないだろうし、それほど突っかからなくてもいいのではないかと思ったからだ。既存の社会の価値観が壊れていくなかで、就職はたしかに難しい話ではあるけれども、もう少し余裕があってもいいんじゃないかなあ。世の中、妄想だけで生きてる人がいるというのに・・・。学校で教えるべきことは妄想力を鍛えることなのかもしれない。

木曜日, 7月 31, 2008

【EC開発体験記-SaaS2-】 リスクをどう考えるか このエントリーを含むはてなブックマーク


 今回は、前記事で説明できなかった、SaaS利用における安全性・信頼性の話をしたい。SaaSは、以下のように、提供者、利用者の両方に大きなメリットがある、すばらしいモデルである。(太字が重要なキーワード)
 サーバやソフトウェアへの初期投資は必要なく、トランザクションが増大してもシステムリソースの心配はない。また、必要なサービスだけに絞って開発することで初期投資を抑えることができる。

 1)個々のユーザが本当に必要な機能のみオンデマンドにカストマイズして利用できる
 2)クラウドコンピューティングを利用することで、スケーラビリティのあるコンピューティングリソースを使うことができる
 3)利用した分だけを支払う従量課金により投資リスクを軽減できる


 こんな魅力的な概念ではある一方で、カストマイズがどれだけ可能かという話や安全性・信頼性の問題があるのは周知の事実である。カストマイズの話は前記事でも触れたのでここでは省略する。
 安全性・信頼性の問題は、主にはサービスレベルの保証と情報漏えいリスクの問題だ。
 サービスレベルの保証については、実際にAmazon S3の障害事例が起きたときのことを考えてみるとよい。Amazon S3、何も日曜に落ちなくてもいいのに!
 とにかく、サーバがダウンしたときに自分たちで復旧できないことが一番痛い。アマゾンEC2などのクラウドコンピューティング利用を考えるのであれば、サーバのサービスレベルはアマゾンに依存することになるが、それはつまり、アマゾンが止まればサービス提供者でさえ何もできず「ただ復旧を祈るだけ」となることを意味する。
 私たちのプロジェクトにおけるサーバ利用はどうだったかというと、1.自社サーバルーム内、2.都内S社ハウジング、3.都内P社(セミ)ハウジング、4.都内P社ホスティング、5.関西S社ホスティングの5箇所で、家具や家電、DVDサイトをそれぞれの場所で運用したのだが、やはり運用上一番安心なのは自社サーバルーム内であった。(運用上安心というのは、サーバがダウンした際にすぐにリカバリーできるかということであり、セキュリティが確保されているという意味ではない。)安心な順でいえば、1>2>3>4>5といった感じだ。
 システムはトラブルがつきものであり、すばやくリカバリーできるかどうかは運用上とても重要なことである。実はこんな苦い経験もあった。
 ドロップシッピングサイトは5.関西S社ホスティングで運用していたのだが、トラブルでシステムダウンしたことがあった。再起動しても復旧しないし、最悪なことにログインすらできなくなった。その日受けた千件の受注を処理できないどころか、データを取り出すことさえできないのである。ホスティング会社に連絡してなんとか対応してくれるように頼んだものの「不可能です」の回答。私はとても後悔した。そもそも、月額数千円のホスティングサーバをクリティカルな業務に使用してはいけない。しかも千件/日という多くの受注を処理するなんて無謀である。その後、単なるパスワード間違いでログインできなかったことがわかり復旧できたのだが、本当に顔面蒼白事件であった。同様のことが4.都内P社ホスティングで起きたのだが、このときはサーバ管理しているビルまで上がりこんで責任者に懇願して事なきをえた。こんな事件が頻発したこともあり、ホスティングはだめだなと思い新規に契約したのが2.都内S社ハウジングであった。ここは自由に入管可能であり、都内なので直ぐいくことができる。しかし、サーバやルータは自前で用意しなければならず、家賃も高いのが問題だ。今のところ、コスト面、運用面で優れていると思っているのが、3.都内P社(セミ)ハウジングである。サーバはP社製のものを購入する必要はあるものの、サーバ費用も家賃も安い。入管も可能なので安心である。一方、クラウドコンピューティングはどうかというと、安心度は5.以下というところが実情だろう。最低でも冗長構成の仕組みを導入してトラブルがあっても止まらないようにするか、あるいは、別のサーバを使ってすぐに復旧できる仕組み作りは必須だと思う。

 次にデータの安全性確保について。

 Gmailを会社の標準にしたいが情報漏えいした場合に保証がないのでできないという話もよく聞く。ある大手の会社では、社内基幹サーバを大手ベンダーのアウトソースしており、情報漏えいがあった際には、アウトソーサーが賠償責任を負うことになっている。こういった契約をGoogleと結ぶことはまず無理である。なので、大手の会社は自分たちのリスクとして負わないかぎり、Gmailを利用することはできない。実際のところアウトソーサーよりGmailの方が安全なのかもしれないが自社へのリスク回避ということで考えると賠償責任を求めることができないと採用されないだろう。でもまあ、これは現在がこのような状況であるというだけで、将来的は賠償責任がなくても、もっと利用されていくだろうと楽観的に考えている。それは確信はまだ持てないが、SaaSを利用した企業の競争力は無視できないほど高まると思うからである。
 一方のSaaSの提供者は、賠償責任までとはいわないまでも、以下のような認定をとるなど、安全対策についてはかなり努力しないといけないだろう。

ASP・SaaS安全・信頼性に係る情報開示認定制度

 最後に、実際に事故が起きたときのために賠償保険に入るのはよいことだと思うのでちょっと紹介したい。

法人/企業向け賠償責任保険

 当社のような小さな会社は特にいえることだが、大手の責任転嫁を簡単に受け入れてはいけない。個人情報保護は多くのところが無限責任を要求してくるが、契約したいからといって簡単にサインしてしまってはだめだ。事故は絶対に起こらないとは限らないので、お客様にきちんと説明して賠償責任保険に入るようにしよう。小さな会社に無限責任を押し付けたって実際に責任を取れるわけではないので、保険に入ることが結果的にお客様への利益にもなるのだ。「いやいや、うちの会社は無限責任を要求することはまずないから、入る必要はないよ」とお客様がおっしゃるのであれば、契約書の無限責任の条項を削ってもらうようにしよう。

火曜日, 7月 29, 2008

【EC開発体験記-SaaS-】 カストマイズは本当に可能なのか このエントリーを含むはてなブックマーク


 今回の話はSaaSである。私は当初、自社基幹システムを含めたサービスの利用、つまり、システムをすべて外部業者に委託できないかということを真剣に考えていた。そもそも、私の本来の目的はECを開発するのではなく、ビジネスを黒字化することであったので、極端な話、EC事業をサービス化した外部の会社があって、サービスを利用できるのであれば開発しなくてもよかったのだ。実際に自社サイト以外にも、楽天やYahooといったモールを利用しており、これで全て業務ができるのであれば結構な話である。しかし、実際には下図のようにEC機能としては十分ではあるものの、出荷、販売管理といった機能がないため、自社の基幹システムに受注を取り込む必要があった。





 SaaSは定義が曖昧で分かりにくいという話はよく聞くので、次の記事を参照いただきたい。ASPとの違いという観点でよくまとまっている。

代表的なSaaS企業といわれているSalesForce.comのモデルを説明した記事(SaaSの衝撃(2) セールスフォースの凄み

セールスフォースの提供するSaaSは従来のASPとは異なる。なかでも特筆すべきは、ASPが抱えていたカスタマイズの難しさや、外部のアプリケーションとのデータ連携、 システムの信頼性への疑問といった問題点を解決したことだ。


 逆の言い方をすると、自社システムを用意しなければならないのは、カスタマイズの難しさや、外部のアプリケーションとのデータ連携があるからである。外部の業者、例えば、楽天やヤマトのシステムに集約できれば話は簡単であるが、より多くの機会やきめ細かなサービスをやるためには、サイトも配送業者も一つだけでは不十分である。実際のプロジェクトでは、様々なフロントエンドのECシステムとの連携、および、大・中・小の3つの配送業者のシステムとの連携を行う必要があったので、自社基幹システムを開発することになった。
 もっといえば、商品によってもサービスが異なってくる。今回のECシステムでは、家電の受発注品を皮切りに、家具、DVD、そしてドロップシッピングと、様々な商品に対応すべく拡張していったが、それぞれにおいて受注の方法、管理の方法が異なるため、実際にはまるで別のシステムをゼロから構築するかのようにカストマイズせざるを得なかったのである。私はECについては素人であり、商品が異なっても仕組みはどれも似たようなものだろうと甘く考えていた。まさかここまで違いがあるとは思いもしなかった。一例を挙げよう。

 <家電受発注品>
  1) 注文を受けてから発注するので納期は曖昧である(3日~7日)
  2) 基本的に在庫管理しない。(受注を受けられれば配送できる)
  3) 小物であればお客様もお届け日の指定にこだわらない
  4) 粗利が低いため、配送料は配送地域によって差をつけたり、高額商品であれば無料にする
  5) ドロップシッピング品においては返品が不可

 <家具>
  1) 家具は在庫管理が必須である
  2) 大物が多いため納期はお客様指定の日を厳守する
  3) 配送管理におけるLT計算が必要になる
  4) 商品の大きさにより配送料をきめる。また、設置、組み立てサービスを別途用意する

 <DVD>
  1) 予約販売が必須であり在庫管理が必要
  2) 発売日の発送を厳守する
  3) 受注を受けてから配送までの時間が長い(半年などもある)ため、すべての商品を発送しないうちに代金を回収することがある(一品発送をもって決済)
  4) 発送枚数が1枚であれば配送料を安くできる(メール便)


 それから、大切なことで細かい話ではあるが、ぜひ気をつけておきたいことに、受注の括り方と金額の計算方法の統一化がある。受注金額の計算では、一品一葉単位であれば商品ごとに消費税が加算された合計となるが、受注単位であれば受注金額の和で消費税計算をすることになる。また、小数点以下は切り上げ(常にお客様が得になるように計算する)とするなど、計算方法がシステム全体で統一されていないと不具合となる。
 先ほど、もし、モールに受注・出荷・販売といった管理機能があればとはいったが、これだけの違いを吸収するだけでも大変である。ASPであれば、パッケージソフトのように出来ることだけを選択すればよいが、カストマイズを前提にしたSaaSでは、基本は全ての要求に応えなければならないだろう。そうなると果たしてどこまで対応できるか疑問である。
 たしかに、費用と時間をかければ不可能ではない話ではあるが、SaaSは多くの顧客に対応することでスケールメリットを追求するモデルなので、それほど多くをかけられないというのが現状だろう。また、通販というビジネスモデルを考えると妥協できないのは運用コストである。結局、システム運用・保守に多くのコストをかけてしまうのであれば、SaaSにする意味がなくなってしまう。
 これについては、私たちのプロジェクトに当てはめてみると反省すべき点が多い。そもそも当初の目的である受注センターの効率化のために、異なる商品の受注業務を一本化して、システムと人の統合を進めなければならなかった。しかし、実際は家電・家具・DVD用のそれぞれの受注システムとスタッフが縦に並ぶ構造になってしまった。商品の特徴を知るバイヤーを中心に販売方法を決めていたこともあったので3チーム編成は悪くはなかったとは思うが、受注スタッフの縦割りは明らかに非効率であった。ここでも書いたが、コストを最小化できてはじめて利益を高めることができ、あるいは、お客様にサービスとして還元できるようになる。もう少し踏み込んだシステム統合、および組織改革は必要だったと思う次第である。

 なお、SaaSの利用については、安全性・信頼性といった問題も考えなければならない。これはまた別の機会に触れたいと思う。

ASP・SaaS安全・信頼性に係る情報開示認定制度

月曜日, 7月 28, 2008

【IBM sMash】 Groovy コンファレンス 2008 このエントリーを含むはてなブックマーク


この話を中心に、sMashとReflexのコラボについて話す予定なのでぜひ見にきてください。デモもやるけど、System-iでできるといいなあ・・・。2008年8月22日 sMash(ProjectZero)と「Reflex」で構築するお手軽営業支援システム @IBM SWCOC セミナールーム 渋谷区道玄坂一丁目12-1(渋谷マークシティウェスト18階)

金曜日, 7月 25, 2008

【Reflex】 ResourceOperator公開を機にあらためて紹介 このエントリーを含むはてなブックマーク



 トランザクション処理に対応したResource Operator、Resource Locator、また、Source Generatorが完成したので近日中に公開したいと思う。Reflexフレームワークについては何度か説明してきたが、これを機にあらためて概要を説明したい。上図にあるように、Reflexは、3つのサーバコンポーネントとクライアントのコンポーネントから構成される。
 まず、Reflex Coreに含まれる、Resource Mapperは、シリアライザー・デシリアライザーで、XMLやJSONからJavaBeanに変換するAPIをもつ。
 Reflex Operatorは、Entityに対してCRUD操作ができるようにするためのコンポーネントで、ローカル/リモートを意識することなく呼び出せることが特長である。リモートのEntity呼び出しは、JNDIにURIが登録されているかどうかで判別される。現在のところ、トランザクション処理はローカル呼び出しのときのみ有効となる。(そのうちリモートでも対応する)
 Resource Locatorは、EntityのURIを解決するためのコンポーネントである。これ自身がEntityとして振舞い、JNDIにURIを登録・削除する機能をもつサービスとなる。Source Generatorは、項目の文字列からEntityのソース、およびSQLを自動生成するツールである。CoCを前提にしたフレームワークでは、マッピングなどで名前が厳密に定義されないといけないため、自動生成ツールは必須だと考え作成した。
 Reflex Viewは、Entityの内容を元にHTMLやPDFを生成するコンポーネントである。HTML生成はブラウザのJavaScript、PDF生成はサーバのライブラリ(Reflex iText)で動作する。詳細はこちらを参照願いたい。ワンソースマルチビューを実現する



 上図は、Reflexの各コンポーネントを配置したものである。一番大きな水色枠がサーバ一つを表している。サーバは、Tomcat、Jettyを想定しているが、Resource LocatorはJettyでないと動かない。紫の枠がReflexのフレームワーク、緑の枠が自動生成されたコンポーネントなので、手で書く必要のあるものは、実質、黄色のBlogicだけである。
 今回公開予定のResourceOperatorは、プロジェクト開発の経験、反省点を踏まえて改良したものである。トランザクションスクリプトパターンを採用しており、成功時にだけコミット、失敗してExceptionがスローされたときにロールバックされるようになっている。このように、トランザクションを開発者に管理を意識させないように工夫している。トランザクション管理はDIを使ったものが主流だが、Reflexではコンパクトにしたかったので使用していない。今回は、Resource Operator、Resource Locator、Source Generatorの3つを公開する。Reflex iTextはサービスとして公開予定なのでオープンソースにはしないがReflex View(HTML)はそのうち公開する。また、Reflex MapReduceもサービスとして公開予定である。
 


 ここでちょっと上図を見てほしい。これは、2004年にXMLコンソーシアムで発表したときのプレゼン資料の抜粋であるが、Reflexのコンポーネント図とよく似ていることがわかるだろう。当時はSOAPが主流だったので通信のコンポーネントにAXISを使い、バインディングツールにはCastorを使っていた。Castorを使った開発はすばらしく生産性が高かったので、その後のReflexの開発の動機となった。
 この図は、暗号化用のXSS4Jなどが含まれているものの、Reflexと比べると複雑である。また、AXISでは、クライアントとサーバは明確に区別され1対1で接続されるが、Reflexでは、サーバとクライアントが対称で区別せず、1:多で複数のコンポーネントを接続できる。
 


 最後にぜんぜん関係ない話をひとつしたい。
 2005年のプレゼン資料をあさっていたら、AJAXとRCPの比較なんてものがでてきた。当然、間違いだらけである。これを今訂正するとしたら、「ビジネスロジックを分担するまでには至らない」だろうか。Reflex Viewにおいても、ブラウザ上のJavaScriptビジネスロジックを認めている。
 逆に、「高い技術力、センスが要求されるので、一部の開発者でなければ無理」は、概ね当たっているのではないだろうか。開発者のスキルあるなしで、特にパフォーマンスの面で雲泥の差となる場合が多い。AJAXが注目されつつある時期だったのでRCPをひいきめに書いた記憶がある・・それでもAJAXは大流行したのであるが・・・・。
2005年プレゼン資料

土曜日, 7月 19, 2008

【EC開発体験記-ビジネスモデル-】 メーカ直送は究極の通販モデル このエントリーを含むはてなブックマーク




 通販業者は店舗をもたないため、その分経費がかからず、製品開発やマーケティング、お客様サービスに費用をまわせるというメリットがある。・・・少なくとも消費者からはこのように思われている。しかし、実際にはコールセンターを含めた受注コスト、商品開発コスト、物流コスト、システム運用費用など多くのコストがかかっている。うまくいっていない通販ほど、かかるコストが大きく、売れば売るほど赤字という悪循環に陥っているのが実態である。私が担当したときの会社はまさにこのような状態であった。通販事業を行うにあたって最も注意を払うべきことはコストである。コストを最小化できてはじめて利益を高めることができ、あるいは、お客様にサービスとして還元できるようになる。こう信じたからこそ、まず最初に取り組んだのはコスト削減、とりわけ、物流改革による運賃、管理費の削減であった。具体的には次の2点を中心に取り組んだ。
 
 1)外部業者に委託していたロジスティクス業務を自社にて行う
 2)メーカから直接お客様に届ける仕組みを構築する

 外部ロジスティクスをやめることは大きな決断であった。そこにかけていた経費を考えるとどうしても避けては通れないことは明らかだったが、増員を見込めない自社で本当にできるかどうかわからず、大きな不安があった。不必要な業務があることが分かっていたので廃止していくものの、それだけでは不十分である。抜本的に業務を削減するには、システムによる自動化・省力化を図る必要があった。また、小・中の商品については、必ずしも大・特大と同じ配送業者を利用する必要はなかったので、小・中を得意とする配送業者と新たに契約することで配送コストを下げた。さらには、メーカに集荷して直接お客様にお届けする、メーカ直送という仕組みを導入することで管理コストを下げた。これらにより、物流コストを1/3に抑えることに成功したのである。
 システムによる自動化・省力化については、「一品一葉」という概念の導入と徹底した配送管理によって実現している。大・中・小と様々な商品を一度に購入されたお客様でも、システム上は「一品一葉」で管理されているため、各配送業者に注文が分かれても問題なく処理できるようになっている。また、配送管理については、まず出荷指示を配送業者のシステムに送信することで集荷依頼を行い、次に、一品ごとの状態(出荷済、配達完了等)を配送業者のシステムから受信することでステータスを更新する。返品の際も、配送業者に返品の指示をデータ送信するだけでお客様のところに集荷してメーカに返品してくれる。
 さらには、お客様のお届け日を指定でき、Webサイトからでも電話注文でも指定可能となっている。この仕組みはお届け日から逆算して、メーカへの出荷指示日を計算できるテーブルを作成しているからできているのだが、配送業者も興味を示すほどの仕組みであり、おそらく他に実現しているサイトはないと思われる。このように、配送システムとの自動連携により、管理の自動化ができているのである。
 実際に省力化が実現できたとき、そのコスト削減額は想定よりもはるかに大きいことがわかり、あらためてシステム化による効果の大きさを知った。
 今回、暮らしのデザインはニッセンの新しい一員となったが、ニッセンの社訓の最初にはこのように書かれてある。

1.常に物流を改善していく


木曜日, 7月 17, 2008

【iPhone】 2つの見方 このエントリーを含むはてなブックマーク


以下の順番で読むと面白い。
iPhoneという奇跡
iPhone 3G のダメダメなところまとめ
携帯電話として見るとつまらんものらしい。PCとして見ても同様である。
では、iPhoneをなんで欲しいと思うか。それが問題だ。

【IBM sMash】 ReflexとsMashによるソリューションの提案 このエントリーを含むはてなブックマーク



 これは先日あるお客様に相談されて考えたものだが、もったいないので当社のソリューションにすることにした。そのお客様は中型汎用機(IBM System-i)のユーザであり、数十箇所の拠点ごとにシステムを置いている。本提案は各拠点のサーバをマッシュアップしてやろうというものだ。具体的な内容は以下のとおりである。また、デモをこちらで配布している。=>PMS Demo

<現状>
 1.営業支援システム(SFA)が各拠点にあるIBM System-iのうえで動いている
 2.営業はこのシステムを使って日報入力や在庫検索をしているが、拠点に戻らないと使えないので不便を感じている。

<相談内容>
 1.営業支援システムをインターネットに接続して携帯端末から使えるようにしたい

<課題>
 1.各拠点に分散したサーバへのアクセス手段がない
 2.各拠点にFirewallなどのN/W設備やアプリケーションを導入しなければならない
 3.そもそも各拠点の基幹システムにインターネットから直接アクセスするのはセキュリティ上問題がある
 4.Webアプリケーションを各拠点にデプロイしなければならない
 5.ネットワークを管理・監視しなければならない

<提案>
 1.代表となるWebサーバ(マッシュアップサーバ)をインターネットに公開する
 2.各拠点のサーバ間はクローズドなVPN内で接続する
 3.マッシュアップサーバから各拠点へのアクセス経路をつくり、拠点の情報はマッシュアップサーバ経由で検索できるようにする。
 4.どの拠点にアクセスするかはマッシュアップサーバで判断する
 5.拠点のサーバはXML(JSON)でデータを返すだけ。検索結果の編集はマッシュアップサーバで行う

<効果>
 1.セキュリティの確保
 2.Webアプリケーションの一元管理
 3.携帯端末に限らず、あらゆるWebアプリケーションに応用可能

 このソリューションは、Webサービス化とマッシュアップ技術により、System-iや汎用機といったプロプライエタリシステムとの連携を可能とするものだ。以前にも述べたが、IBMの既存のお客様はこのケースのように、Web化によって付加価値を見い出せるところが多いはずである。参考
 sMashのSystem-i対応は、IBM開発元ともう少し詰めなければならない部分があるそうだが、そこはsMash開発者の一人である樽澤さんにサポートいただき課題を解決していきたいと思う。

もしこの提案にご興味あれば遠慮なく以下まで連絡ください。

takezaki アット virtual-tech.net

金曜日, 7月 11, 2008

【雑記】 iPhoneの魅力  このエントリーを含むはてなブックマーク



私の結論
  
音楽とソフトの新しい流通網を創る可能性があるから

 ソフトバンクは日本のパソコンの「パッケージソフト」流通網を創り出したが、今度はアップルの手を借りて新しい「ネットワークソフト」流通網を創ろうとしている。これは、どちらかというと提供者側にとって魅力的に感じるところかな。新しいソフトを開発してiPodで儲けてみようと思う者にとっては非常に魅力的なものに見えるだろう。当然、私も魅力的に見える。

でも、消費者が欲しがる動機って何なんだろう?

水曜日, 7月 09, 2008

【JavaScript】 Validate時にfocusを固定する方法 このエントリーを含むはてなブックマーク


 今回は、Reflex Viewを担当している genokitaさんからの嵌りの報告。Reflex Viewで、キー入力時にvalidatorが実行されるところで嵌ったようだ。 validatorは、例えば、値をチェックして「誤」と認められた場合に、入力中のテキストボックスの背景を赤色にしたりカーソルを動かなくしたりする機能である。Reflex Viewの概要およびValidatorについてはこちらを参照していただきたい。
 まず、validatorの基本動作について、A,Bの2つのテキストボックスを例に以下に説明する。2つのテキストボックスにはonblurイベントが登録されており、validatorはonblurイベント駆動になっている。onblurイベントは、カーソルがテキストボックスから離れたタイミングで発行されるイベントだ。


A:テキストボックス
B:テキストボックス

【動作】
Aの値を誤に変更し、Bをクリックする

【前提条件】
(1)両テキストボックスにはonblurがかかっている
(2)blurイベントが発生するごとにvalidationがかかる
(3)validationが通らなかったら、フォーカスを誤セルに戻し、表示される値は誤のまま(=entityの値を入れなおさない)
(4)validationが通ったら再全計算(Blogic()),再描画(Refresh())がかかる

【ロジック】
if (A.blur) {
res = validate(A);
}

if (res == IS_INVALID)
alert("A is invalid!");
A.focus(); --(i)
} else if(res == IS_VALID) {
Blogic();
Refresh();
B.focus();
}

(i)に至るまでには
A.blur;
B.focus;
B.blur;
A.focus;
の順にイベントが発生するので、B.blurの時点でBのvalidationがかかる。



 詳細は以下にまとめるが、今回の問題は、validator実行後にカーソルが移動してしまうことが原因で起きていた。強引にフォーカスを元に戻そうとすると移動先のonblurイベントによるvalidatorが走ってしまうが、元のテキストボックスが「誤」であるから、移動先のテキストボックスが「誤」である場合に無限ループを引き起こしてしまうのである。ちなみに、読込時の初期データに誤りがあっても同様の現象に陥るため、前提条件をクリアーしたデータでないとうまく動作しない。このことは入力点におけるデータチェックの必要性ということで、こちらに書いたとおりである。


【初期データに誤りがある場合】

 A初期:正
 B初期:誤

* Aの値を誤に変更し、Bをクリックしてしまうと、AとBが交互にエラーを吐くため無限ループに嵌る(←ここで嵌った)
* 初期データは正しいという前提で黙認(本当なら初期データが正しいかどうかをチェックしてあげなきゃいけない)

【初期データが正しい場合】
 
 * Aに誤を入れた後にBをクリックすると、A.blurで最初のvalidationが実行され、B.blurの時にも再びvalidationがかかる
 * blurの度にvalidation再計算再描画が行われるため、B.blurの時にAの値がentityの値で置き換えられてしまう(←ここで嵌った)



 解決策としては、以下のように、本来格納すべき場所(Entity)以外の一時的な値を保持する領域を用意すればよい。Reflex Viewでは、これをpreview層と定義している。preview層に入っているデータはまだValidatorを通っていないためエラーの可能性がある。Validatorでエラーでないことが確認できればEntityの値を書き換えることができる。



【解決策】

* 誤Aの値をどこかに格納しておき、再描画の際にentity.Aを誤Aで置換してやればいい
* Validator(View)クラスを作成し、誤セルのID・値・エラーコードを静的保持しておけばいい




火曜日, 7月 08, 2008

【JavaScript】 DOMをXMLに変換 このエントリーを含むはてなブックマーク


 今回のXML変換は連想配列ではなくDOMから変換するものだ。これは、Googleのコードを抜粋したもので、2006年頃に使っていたものだが、DOMはあまり好きではないので捨て置いていた。しかし、この記事が好評なので紹介することにした。さらに連想配列に変換したければjson.jsを使うといいだろう。
dom2xml.html DEMO(ココをクリック)

<dom2xml.html: サンプル>


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>DOM2XML</title>
<script type="text/javascript" src="scripts/xmltext.js"></script>
<script type="text/javascript" src="scripts/xmlgetx.js"></script>
<script type="text/javascript">

function test() {

 var collection = document.createElement('collection'); // ルートエレメントを作成
 collection.setAttribute('title','Java Script XMLDOM test'); // 属性を作成

 var membertype = document.createElement('member-type');  // 子要素を作成
 membertype.appendChild(document.createTextNode('jpeg')); // 子要素のtextnodeを作成
 collection.appendChild(membertype);            // 親要素に子要素を追加

 var xmltext = xmlText(collection);   // xmlのタグを含む形でテキスト化
 alert(xmltext);

 var xmlvalue = xmlValue(collection);  // xmlのタグを除く形でテキスト化
 alert(xmlvalue);
  
 var title = xmlGetAttribute(collection, 'title'); // xmlの属性を取得する
 alert(title);

 var membertype = xmlGetText(collection,'member-type'); // xmlのtextnodeを取得
 alert(membertype);
  
}

</script>
</head>
<body onload="test();"></body>
</html>


<xmlgetx.js: 属性やテキストノードを取得する(クリックでソース参照)>

function xmlGetText(node,tagname) {
 var result = node.getElementsByTagName(tagname).item(0).childNodes.item(0).data;
 return result;
}

function xmlGetAttribute(node, name) {
 // TODO(mesch): This should not be necessary if the DOM is working
 // correctly. The DOM is responsible for resolving entities, not the
 // application.
 var value = node.getAttribute(name);
 if (value) {
  return xmlResolveEntities(value);
 } else {
  return value;
 }
};

// Split a string s at all occurrences of character c. This is like
// the split() method of the string object, but IE omits empty
// strings, which violates the invariant (s.split(x).join(x) == s).
function stringSplit(s, c) {
 var a = s.indexOf(c);
 if (a == -1) {
  return [ s ];
 }
 
 var parts = [];
 parts.push(s.substr(0,a));
 while (a != -1) {
  var a1 = s.indexOf(c, a + 1);
  if (a1 != -1) {
   parts.push(s.substr(a + 1, a1 - a - 1));
  } else {
   parts.push(s.substr(a + 1));
  }
  a = a1;
 }

 return parts;
}

// Copyright 2005 Google Inc.
// All Rights Reserved
//
// An XML parse and a minimal DOM implementation that just supportes
// the subset of the W3C DOM that is used in the XSLT implementation.
//
// References:
//
// [DOM] W3C DOM Level 3 Core Specification
//    <http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/>.
//
//
// Author: Steffen Meschkat <mesch@google.com>

// NOTE: The split() method in IE omits empty result strings. This is
// utterly annoying. So we don't use it here.

// Resolve entities in XML text fragments. According to the DOM
// specification, the DOM is supposed to resolve entity references at
// the API level. I.e. no entity references are passed through the
// API. See "Entities and the DOM core", p.12, DOM 2 Core
// Spec. However, different browsers actually pass very different
// values at the API.
//
function xmlResolveEntities(s) {

 var parts = stringSplit(s, '&');

 var ret = parts[0];
 for (var i = 1; i < parts.length; ++i) {
  var rp = stringSplit(parts[i], ';');
  if (rp.length == 1) {
   // no entity reference: just a & but no ;
   ret += parts[i];
   continue;
  }
  
  var ch;
  switch (rp[0]) {
   case 'lt':
    ch = '<';
    break;
   case 'gt':
    ch = '>';
    break;
   case 'amp':
    ch = '&';
    break;
   case 'quot':
    ch = '"';
    break;
   case 'apos':
    ch = '\'';
    break;
   case 'nbsp':
    ch = String.fromCharCode(160);
    break;
   default:
    // Cool trick: let the DOM do the entity decoding. We assign
    // the entity text through non-W3C DOM properties and read it
    // through the W3C DOM. W3C DOM access is specified to resolve
    // entities.
    var span = window.document.createElement('span');
    span.innerHTML = '&' + rp[0] + '; ';
    ch = span.childNodes[0].nodeValue.charAt(0);
  }
  ret += ch + rp[1];
 }

 return ret;
}



<xmltext.js: DOMをXMLテキスト化する>(クリックでソース参照)

// Based on
// <http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247>
var DOM_ELEMENT_NODE = 1;
var DOM_ATTRIBUTE_NODE = 2;
var DOM_TEXT_NODE = 3;
var DOM_CDATA_SECTION_NODE = 4;
var DOM_DOCUMENT_NODE = 9;
var DOM_DOCUMENT_FRAGMENT_NODE = 11;

// Returns the text value if a node; for nodes without children this
// is the nodeValue, for nodes with children this is the concatenation
// of the value of all children.
function xmlValue(node) {
 if (!node) {
  return '';
 }

 var ret = '';
 if (node.nodeType == DOM_TEXT_NODE ||
   node.nodeType == DOM_CDATA_SECTION_NODE ||
   node.nodeType == DOM_ATTRIBUTE_NODE) {
  ret += node.nodeValue;

 } else if (node.nodeType == DOM_ELEMENT_NODE ||
       node.nodeType == DOM_DOCUMENT_NODE ||
       node.nodeType == DOM_DOCUMENT_FRAGMENT_NODE) {
  for (var i = 0; i < node.childNodes.length; ++i) {
   ret += arguments.callee(node.childNodes[i]);
  }
 }
 return ret;
}


// Returns the representation of a node as XML text.
function xmlText(node) {
 var ret = '';
 if (node.nodeType == DOM_TEXT_NODE) {
  ret += xmlEscapeText(node.nodeValue);
  
 } else if (node.nodeType == DOM_ELEMENT_NODE) {
// midified by S.Takezaki(to LowerCase)
  ret += '<' + node.nodeName.toLowerCase();
  for (var i = 0; i < node.attributes.length; ++i) {
   var a = node.attributes[i];
   // midified by S.Takezaki
   if (a && a.nodeName && a.nodeValue && a.nodeName!='contentEditable' ) {  
    ret += ' ' + a.nodeName.toLowerCase();
    ret += '="' + xmlEscapeAttr(a.nodeValue) + '"';
   }
  }

  if (node.childNodes.length == 0) {
   ret += '/>';

  } else {
   ret += '>';
   for (var i = 0; i < node.childNodes.length; ++i) {
    ret += arguments.callee(node.childNodes[i]);
   }
// midified by S.Takezaki(to LowerCase)
   ret += '</' + node.nodeName.toLowerCase() + '>';
  }
  
 } else if (node.nodeType == DOM_DOCUMENT_NODE ||
       node.nodeType == DOM_DOCUMENT_FRAGMENT_NODE) {
  for (var i = 0; i < node.childNodes.length; ++i) {
   ret += arguments.callee(node.childNodes[i]);
  }
 }
 
 return ret;
}

// Escape XML special markup chracters: tag delimiter < > and entity
// reference start delimiter &. The escaped string can be used in XML
// text portions (i.e. between tags).
function xmlEscapeText(s) {
 return s.replace(/&/g, '&').replace(/</g, '<;').replace(/>/g, '>');
}

// Escape XML special markup characters: tag delimiter < > entity
// reference start delimiter & and quotes ". The escaped string can be
// used in double quoted XML attribute value portions (i.e. in
// attributes within start tags).
function xmlEscapeAttr(s) {
 return xmlEscapeText(s).replace(/\"/g, '"');
}

// Escape markup in XML text, but don't touch entity references. The
// escaped string can be used as XML text (i.e. between tags).
function xmlEscapeTags(s) {
 return s.replace(/</g, '<;').replace(/>/g, '>');
}


<関連>
 DOMをXMLに変換するjQueryプラグイン

月曜日, 7月 07, 2008

【EC開発体験記-View-】 ワンソースマルチビューを実現する このエントリーを含むはてなブックマーク


 Reflexフレームワークの真骨頂はViewである。Reflexは、ブラウザ、携帯、Flex2、PDFなど様々なViewに対して、同じEntityを与えさえすれば、同じように表示させることを目標とする。以前にも述べたが、1つのEntityから複数の写像をとれるという意味で、名前をReflex(写像)とした。これをワンソース・マルチビューというらしい。(xfy by ジャストシステムさん)
 本来は、EntityをマッシュアップするControlerがViewからの参照元なので、ControlerはViewを元に設計すればよいことになる。さらには、EntityもViewを意識することで、Viewのイメージを崩すことなく一貫して扱えるようになるので、このような設計を心がけるべきだろう。実際に私たちがプロジェクトで構築したシステムにおいては、理想とはいえないものの、一貫したEntityの利用が一応できている。例えば、Flex2のカートから受注プロバイダにXML送信された受注(Entity)は、管理画面のPHPからJSONリクエストで呼ばれることもあれば、納品書PDFとして出力されることもある。否定の3.を参照。
3.各サブシステムで共通のエンティティを使用(最小単位は1品1葉1レコード)

 それでも理想といえないのは、統一したレンダリング方法がないからである。一つのテンプレートを作成するだけで、Flex2やPDFなどの様々な表現に対応させたかったのだが、そこまではできておらず、それぞれのViewに応じた実装とスキルが必要であった。
 そこで、現在開発中のReflex Template(コードネームv:lex)では、XHTMLによるテンプレートを一つ作成するだけで様々な表現ができるようにした。考え方としては、下図のように、EXCELのようなスプレッドシートのイメージでViewを設計し、1枚のシートをdocument、ページをpage/tab、表をtableとして配置していく。tableはページに何個でも置くことができ、位置も細かく指定できる。なお、表題などの文章や画像などは1マスの表とする。
 Templateは動的に選択でき自由に画面遷移できる。また、TABを利用することで、遷移なし(ページ読込なし)に異なる画面を表示させることができる。セッションという考えはなく、Entityにデータを保存する。つまり、Entityはグローバルであり共有データとしても使う。Entityを与えてBlogicを実行するが、BlogicはクライアントJavaScriptでも、サーバのサービスでもどちらにあってもよい。



 また、以下のような5階層のレイヤーをもち、Template Engineによって各層が組み立てられる。


<Template Engineの機能>

1.Templateを選択する機能
- page id(xxxx.html#001)で指定
2.サーバからEntityを取得しBlogicを実行する
- Entityをチェックしてエラーコードをセットする。その後はAttributeによってエラーが表示される
- エラーがなければBlogicを実行する
3.静的なtemplateの上に動的なEntityをマッピングする。その際、Attributeにより値をフォーマットする(カンマを付けたり色を付けるなど)
4.データ入力時にvalidatorを実行する
5.(エラーが含まれていなければ)Entityをサーバに送信する


 一つだけ注意すべきことがある。それは、Entityへのデータ入力点(この場合、サーバから取得した時点とキー入力の時点)におけるエラーチェックを厳密にすることだ。キー入力の時点では、Validatorとして古くからJavaScriptは一般的に使用されてきたが、サーバから読込んだ時点というのは意外な方も多いだろう。この設計では、ここをチェックしないとハングする場合もあるので要注意だ。というわけで、否定の16はやはり間違いであった。

16.入力は寛大に受け付け、出力は厳しくせよ

 なお、Blogicにおいてエラーとなった場合には、エラーコードをスタックして返すようにする。エラーコードの種類により、Alertを出したり、色を変えたりするのはValidatorではなく、Attributeの責務である。
 詳細は完成してから発表したい。乞うご期待。

日曜日, 7月 06, 2008

【雑記】 MapReduce HTTPMR、Groovy、Flashインデクス化など このエントリーを含むはてなブックマーク


これまで概念だけが先行していたWeb2.0。ここにきて実装技術が続々と登場してきている。そこで、最近の気になる記事をピックアップして、レイヤー(View、Mashup、Resource)ごとにまとめてみた。レイヤーについては、これを参照いただきたい。 

まず、View層のトピックでは、ちょっと地味かもしれないが・・Flashのインデクス化。小川さんのコメントケータイに及ぼす影響が大きいという意見。iPhone登場もあり、今後のITビジネスの中心はケータイになると思われるので同意。

次に、Resource層では、MapReduce HTTPMRの登場。
HTTPベースによるMapReduceフレームワーク「HTTPMR」
MapReduceについては、ここや、ここでも触れたが、インターネット分散環境におけるデータの扱いということで、レスポンスが返らなくてもよしとするとか、狂ったデータを無視するとか、ちょっと特殊なテクニックを要する。

Groovyは、Mashupにおける実装技術の一つ(と強引に考えたのは、sMashで採用されているからに他ならない)Groovyについては、NetBeans 6で学ぶGroovyとGrailsがわかりやすい。

 上記は特にSaaSにとっての朗報かと思う。高い生産性をもたらすスクリプト言語がJavaに影響した結果Groovyができた。HTTPMRに関して言えば、Google App Engineを活用しており無償で動作する。サーバ管理がある以上、Cheap革命はオープンソースを使うだけでは成し遂げられないが、Google App EngineやS3などは一つの解でもある。一方、個人情報の扱いなどの制約があり、エンタープライズでどう活用していくかが課題である。

金曜日, 7月 04, 2008

【本日の嵌り】 Google八分? このエントリーを含むはてなブックマーク


先日のsMashの記事がGoogle八分にあったようだ。単なるランク落ちと思われがちだが見分け方はある。
googleでinfo:をつけて検索すればよい。以下の2つの違いは見てみると分かるが、ibm-smash.htmlでは、「~に関する情報は見つかりませんでした。」と出る。


info:http://www.virtual-tech.net/blog/2008/07/javascript-log4js.html
info:http://www.virtual-tech.net/blog/2008/06/ibm-smash.html



Google の サイトの登録のページ で、もう一度登録してみるといいとのこと。

これで解決。

木曜日, 7月 03, 2008

【JavaScript】 ローカルに保存する機能をもつLog4js このエントリーを含むはてなブックマーク


 Log4jsのFileAppenderを発見。なるほど、ローカルに書き出しはこうすればよいのか。
こちらで、富田さんがブラウザからのローカル保存に触れられていたので興味はあったけど、今までやり方が分からなかった。ちなみに、こんなこともできるらしい。
業務系Webアプリでファイルアップロード後にローカルファイルを削除する


Log4js.FileAppender = function(file) {

  this.layout = new Log4js.SimpleLayout();
  this.isIE = 'undefined';
  
  this.file = file || "log4js.log";  
  
  try{
    this.fso = new ActiveXObject("Scripting.FileSystemObject");
    this.isIE = true;
  } catch(e){
    try {
      netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
      this.fso = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
      this.isIE = false; //mozilla & co
    } catch (e) {
      log4jsLogger.error(e);
    }
  }
};


Log4jsはシンプルで実によくまとまっている。prototype.jsからコピーしたと思われる、継承(extend)、bind、それからattachEventがあるだけだ。実際これで基本的なことは大抵できる。
$()も放棄してシンプルに実装している点も感心である。JavaScriptアプリはライブラリをインクルードしないでコンパクトにする、こういったコピペスタイルが流行ってくるのではないだろうか。
追記:というわけで、短いライブラリを作ってみた。=>とっても短いAJAXライブラリ extractive.js


/**
* Object extention (OO) methods if no prototype avaliable.
*
* @private
* @ignore
*/
if(!Object.prototype.extend) {
  Object.extend = function(destination, source) {
   for (property in source) {
   destination[property] = source[property];
   }
   return destination;
  };
  
  Object.prototype.extend = function(object) {
   return Object.extend.apply(this, [this, object]);
  };
}

/**
* Define bind if no prototype available.
* @private
* @ignore
*/
if(!Function.prototype.bind) {
  /**
   * Functions taken from Prototype library,
   * didn't want to require for just few functions.
   * More info at {@link http://prototype.conio.net/}
   * @private
   */  
  Function.prototype.bind = function(object) {
   var __method = this;
   return function() {
    return __method.apply(object, arguments);
   };
  };
}

  /**
   * Attatch an observer function to an elements event browser independent.
   *
   * @param element element to attach event
   * @param name name of event
   * @param observer observer method to be called
   * @private
   */
  attachEvent: function (element, name, observer) {
    if (element.addEventListener) { //DOM event model
      element.addEventListener(name, observer, false);
    } else if (element.attachEvent) { //M$ event model
      element.attachEvent('on' + name, observer);
    }
  }


【JavaScript】 StyleをセットできるBuilder.node() このエントリーを含むはてなブックマーク


scriptaculousのbuilder.jsとほぼ同じ機能だが高速。一括してエレメント(タグ)と属性や内容を指定することができる。また、classNameを指定できるほか、StyleをActiveにゲット、セットできるのが拡張点。
追記:これはname.camelize()を使っており、prototype.jsを必要とする。必要のないバージョンはこちらにある=>とっても短いAJAXライブラリ extractive.js

var Builder = {

   initialize: function() {
   },
   node: function(elementname, param, text) {
     var element = document.createElement(elementname);
      if (param.id) element.id = param.id;
      if (param.className) element.className = param.className;
      if (param.style) {
        for (name in param.style) {
           element.style[name.camelize()] = param.style[name];
         }
      }
      if (text) {
         var value = document.createTextNode(text);
         element.appendChild(value);
      }
      if (param.src) element.src = param.src;
      return element;
   },
   // http://bmky.net/text/note/javascript-css.html
   //
   getActiveStyle: function ( element, property, pseudo ) {
       if( element.currentStyle ) { //IE or Opera
         if( property.indexOf( "-" ) != -1 ) property = property.camelize( );
         return element.currentStyle[ property ];
       }
       else if( getComputedStyle ) { //Mozilla or Opera
         if( property.indexOf( "-" ) == -1 ) property = property.deCamelize( );
         return getComputedStyle( element, pseudo ).getPropertyValue( property );
       }
        return "";
   }
}


top変数により、styleに動的にセットする


var hoge = Builder.node('div', { id : hogeid, className : 'hoge', style : { marginTop : top+'px'} } );



水曜日, 7月 02, 2008

【本日の嵌り】 DNS MXレコード このエントリーを含むはてなブックマーク


Webサーバにメールサーバと同じドメイン名を使ってしまった。これではメールが全部、Webサーバに飛んでいってしまう。Webサーバのメール設定はしていないので、お客様の問い合わせを受け付けられないし、メールの注文もできない。><どうしよう・・・・。

http://example.comと、hoge@example.com

あ、そうだ。DNSのMXレコードがある。

MXレコードを追加すれば別のサーバに配信できる。また、優先度を決めることができ、送信できなかった場合に順番に送信される。ちなみに、WebサーバにはCNAMEをつかうと便利。

これで解決。


example.com. IN A 192.168.1.2
example.com. IN MX 10 mail.example.com.


<参考>
nslookupの基本的な使い方(MXレコード編)
メール/Webサーバを効率的に動かすゾーン設定
CNAMEレコードの使い方

金曜日, 6月 27, 2008

【IBM sMash】 マッシュアップはサーバでいいんですか。いいんですよ。 このエントリーを含むはてなブックマーク


 先日、IBM@渋谷マークシティでsMashの話を伺う機会があった。sMashは、RESTful SOAということで、いわゆるWeb2.0的な今どきのコンテナである。sMashの主な特長は以下のとおりだ。(今回はsMashについて説明するが、私の主観がかなり入っており、IBMの戦略や意図とは異なる部分があることをご容赦願いたい。断定的な言い方にはすべて「~のような気がする」を追加して読むぐらいがちょうどよいだろう。)

* JVMで動作するGroovyとPHPをサポート
* 軽量(LightWeight)なコア
* RESTfulなSOA設計。
* サーバサイドMashup
* クライアントはDoJoだが強要するわけではない
* オープンプロセスで開発されている(オープンソースに近い)
* オープンレポジトリ (by Ivy,Maven2)
* デュアルライセンス(大規模に利用するときに限り有償)

まず、RESTfulについて、URIの設計がリソース中心できれいに設計されている。とてもシンプルで分かりやすい。今流行りつつある、URItemplatesは提案してみたものの、意識すべきかどうか微妙だと思った。必要があればquery parameterを追加すれば十分かな。
クライアントでDoJoを強要するわけではないに同意。リソースから簡単にPDFを作成できる弊社のReflexiTextやクライアント設計モデル(JavaScript版Strutsのようなもの)と組み合わせるといいものができそうだと思った。ivy,maven2レポジトリの採用はとてもいい。
 それから目に付いたのは、JVMで動作するGroovyとPHPのサポート。そして、軽量であること。これには今のLL時代にどう対応するかというIBMの苦悩と戦略を感じる。Project ZEROといわれるように、Web開発のあり方を根本から見直すことで、重厚長大なJava文化を払拭して、スクリプト言語の高い生産性を取り入れる一方、これまで培ったJavaの資産を活用する戦略である。なかには、これまでのJava戦略を覆すとか、SCA・SDOの立場はどうなるとか、疑問・不信を感じられる方もいるかもしれないが、元IBMerである私は、これがとてもIBMらしい一貫した戦略であることが理解できた。なので、ちょっとここで説明させてもらいたい。
 IBMが十数年前から貫いているソフトウェア戦略の一つに、お客様のソフトウェア資産をマルチプラットフォームでサポートするということがある。小さなPCで始めた業務アプリがスケールアップしたときに、メインフレームほどの大きな環境を用意できるのはIBMだけだから、安心してIBMに任せなさいと。しかし、かつてのOS構想(OS/2、OS/400、OS/390)では、統一したAPIでそれを実現しようとしたがうまくいかなかった。ところがJavaはうまくいった。WebSphereとDB2により、すべてのプラットフォームで一度書いたアプリが動くようになった。
 お客様資産の活用とは、お客様自身のデータおよび既存アプリケーションの活用である。それをサクサクとインターネットなどへ適用できれば大きな価値を生み出せる。実際にY社の荷物追跡システムはそうだった。今でこそ一般的に使われているが、当時はお客様自身、その価値に気づいていなかった。これは、お客様資産の活用が困難で、時間やコストがかかっていたなら実現できなかっただろうと思える。
 そこで、さらに一歩すすんだことを考える。マルチプラットフォームでサポートすることがお客様へのコミットメントであるなら何もJavaにこだわる必要はないわけだ。要は、JVMを活用しながら他の言語でもサポートできるようにすることが、お客様資産を活用することにもなる。Javaが敬遠される理由には、納期が長い、融通がきかない(仕様に準拠しないと怒られる)、開発費が高いなどがあるが、LLで解決できるなら、それでよいのではないだろうか。

 また、GroovyとPHPが必要な理由と、サーバサイドWebアプリの否定ではないことを付け加えたい。ブラウザから直接サービスを実行するスタイルが主流になることは認めるが、この記事の後半でも触れているように、XMLを仲介する変換サービスや、ちょっとしたHTMLをサーバサイドで行う必要がどうしてもでてくる。このちょっとしたコーディングはスクリプト言語が最も得意とするところである。

 それから、sMashで最も重要だと思う点は、サーバサイドMashupであり、BCEでいうところのControlに照準を当てているということ。Controlは、ビジネスロジックというと語弊があるが、ワークフローのロジック部分といえばしっくりくる。これまでワークフローとWebアプリは別物のようなもので、開発者の多くはイメージしにくいかと思う。また、この記事でも書いたように、ControlはこれまでModelに無理やり詰め込まれていて、あまり意識せずにWebを開発できた。これまで曖昧で意識もされなかったControlであるが、RESTfulな設計を推し進めることで、相対的にくっきりと浮かび上がってきた。resourceは自己完結した情報の集合として静的に扱われる一方、ControlはBoundaryの要求に応じて動的にresourceを編集する機能となる。このようにきれいに分けることで、いわゆる疎結合開発ができるようなる。従来のWeb設計開発では、画面、ビジネスロジック、データベースの3つに依存した密結合開発であったものを、RESTful設計ではビジネスロジックを外だしにして画面も独立して開発できるようになるのだ。この結果、分業という大きなメリットを享受できるようになった。分業のメリットがいかに大きいかは、この記事で説明したとおりである。
 また、Controlに位置するものは、今どきの言葉でMashupといったが、これには違和感を覚える方もいると思う。私は、Mashupをただの変換・編集とするのは間違いだと考えている。ビジネスロジックを含み、また、マッシュアップしたコンポーネントがさらにリソースと同等になるとさらによい。データ更新にはトランザクション管理を伴い難しいので、現在はまだ変換・編集が多いのも事実だが、今後はビジネスロジック中心に組み込まれていくのは明らかである。私は、Mashupが単なる変換・編集からビジネスロジックを含むControlに役割が変わったときに、本当の意味でオブジェクト指向設計の次の段階に進むことができると信じている。
 これまでにも、ワークフロー設計は、ESBやワークフロー製品で採用されてはきたが、往々にして重厚長大になり、コスト面で引き合わなかった。このような統一したアーキテクチャーのもとではどうしても重厚長大になってしまう。それがRESTful SOAにより疎結合開発が可能になろうとしている。これには統一したアーキテクチャーはいらない。リソースの設計において、コンベンション(規約)を重んじることでインターオペラビリティ向上が図れるからだ。リソースを積み木のように組み合わせて開発する。本当にそれが実現できれば、RESTful SOAは、進化ではなく革命に近いものになるだろう。
 
 最後に当面の課題について触れたい。
 まず、Webアプリ開発者にとっては、どうやってシステムを設計、実装していけばよいか、イメージが伝わりにくい。先に述べたように、Webアプリではそもそもワークフロー的に作る文化がなく、サービス志向、REST志向で作る文化もない。SOAPは古くからあるが少数だ。また、更新系の設計をどうするかも課題だ。コンポーネント間の2phaseコミットは大変かもしれないが、トランザクションスクリプトでシンプルな設計にすべきだと思う。とにかくシンプルにして多くの開発者に支持されないと、SCA、SDO、ESBといったManagedなアーキテクチャスタイルで陥ったように、シュリンクを経験するはめになる。

 Project ZEROは、Incubator Project(将来的に有効に活用できそうな新しいアイディアや技術を模索するプロジェクト)の一つで、Don Fergusonの発案らしい。
Managedなアーキテクチャスタイルに背を向け、IBMを全速力で飛び出してRESTを志向した私であるが、そこで書いた絵はsMashと瓜二つ。お釈迦様(Ferguson)の指に書いた孫悟空、といったところかな。

<追記>
こちらに提案を書きました。
ReflexとsMashによるソリューションの提案

火曜日, 6月 24, 2008

【雑記】 くだらない妄想 このエントリーを含むはてなブックマーク



ジョブズ氏引退後のアップルを考える

ジョブスのいないアップルは、たかた社長のいないジャパネットを想像すればよい。

ジャパネットたかたは日本のスティーブ・ジョブズか?

【本日の嵌り】 いざというときのMySQL関数 このエントリーを含むはてなブックマーク


売上データ送信プログラムが動かない。原因は仕様漏れ。商品以外の送料やポイントを先方指定のJANに変換すべし、ということをついさっき言われた。先方の「お勝手JAN」に変換してあげないとエラーを起こすそうだ。本日から運用開始なのに、そんな大事なこと、もっとはやく言おうよ~。><;

でも、こんなときに頼りになるのがMySQL。ロジックを修正するんじゃなくてSQLに関数を追加すればよい。

select
case when item_code = '送料' then '0000000000001' when ・・・ else jan_code end as jan_code
from table



これで解決。

月曜日, 6月 23, 2008

日曜日, 6月 22, 2008

【EC開発体験記-Entity-】 自己完結したデータの集合 このエントリーを含むはてなブックマーク


 Entityの設計はシステム設計の肝である。だが、構造をどうするかとか、外部参照や冗長をなくすとか、考えだすときりがないし、とにかく時間と労力がかかる。通常は、RDBのERモデル、オブジェクト志向のドメインモデルなどに則って設計することが多い。それぞれ設計ツールも豊富なので利用価値はある。しかし、概念や設計ツールの学習コスト、リレーショナルとオブジェクトの変換コスト、インピーダンスミスマッチは馬鹿にできない。そこで、ここは敢えて頭を空っぽにして、Entityの最低限必要な情報、すなわち、項目および属性とその構造だけに着目して、それらを洗い出すことから始めるとよい。とにかく、最初から難しく考えないで設計を進めていき、問題があればあとで修正する方法をとる。おすすめは、マインドマップの作成だ。(参考:テキストをマインドマップに変換するツール) 
 次に、自己完結した情報となるように項目を括る。とにかく外部参照のないものにするのがコツだ。例えば、カタログ巻末にあるFAX注文書は、受注に必要な情報がすべて記載されており、それだけで自己完結した情報となっている。システム化する際には、1枚の注文書が1インスタンスとなるように、さらには、1インスタンスが1レコードとなるように設計する。こうすれば、Entity=Tableというイメージとなって都合がいい。そのことを、否定の2として挙げた。

否定の2
2.テーブルは第3正規形まで設計しない


 ここは正規化したい気持ちをぐっとこらえるのがポイントだ。また、Entityの階層を2階層とする一方、Tableの項目名をEntityと同一とする。このような規約で縛ることを、設定よりも規約(Convention over Configuration,CoC)という。こうすると、自由度は下がるが生産性は高まる。また、学習コストは少なく、インピーダンスミスマッチを解決できる。もしかしたら、「こんな設計ありえないだろ!」と正論をかざす頑固なRDBスペシャリストが出てくるかもしれないがスルーでよい。そもそも、設計にエネルギーと時間を使わないための規約なのだから、やらなくてもいい議論はやらない方がいい。ここは議論したい気持ちをぐっとこらえるのがポイントだ。

 話は戻り、ここで実際に、ECにおける「受注」について考えてみる。「受注」とは、「誰が何を注文して、いつどこに届けるか」という情報である。これを思いつくままに書くと、以下のようになる。


受注
 受注日時
 注文主
  氏名
  住所
  電話番号
  email
  カード番号
 届け先
  氏名
  住所
 商品
  JAN
  売価
 お届け日時
 配送方法
 決済方法
 ステータス



以上が、とりあえずのEntity設計であるが、見ての通り、突っ込みどころ満載である。
まず、2階層といったくせに3階層になっている。(受注>注文主>氏名)このままではいかんので、規約に則って2階層に修正しなければならない(受注>注文主氏名)
 それから、何気なく列挙している「ステータス」がある一方で、普通あるはずの「受注番号」などのキーがない。意味不明なものがあって、あるべきものがない、という中途半端なものになっている。また、1受注に複数の商品を購入される方もいるはずで、なんらかの「受注番号」で括る必要がある。さらに商品レコードを示すキーも必要になる。(商品レコードは一品一葉という概念で管理されている。これについては別の機会に説明したい)

 次にステータスを説明する。データが妥当(Valid)かどうかはステータスにより管理する。「ステータス」は、簡単にいうと、「受付られた」、「出荷された」等、データがどういう意味をもっているかということを示すためにある。いわゆる「判(ハンコ)」と同じものだ。
 まず、データがValidであれば、判を押された文書のように、それだけで意味を持つようになる。例えば、「ステータス=受付」で、その商品が在庫にあり、指定した日に届けられる予定であることを意味し、「ステータス=出荷」で、商品が出荷され、売上が計上されたことを意味する。

 また、判を押すためには、データの妥当(Valid)を判断するビジネスロジックが別途用意されなければならない。受注を受け付けるための条件は次の通りである。

1)注文主は実在するか
2)商品は存在するか
3)カードの与信はOKか
4)お届け日に配送できるか

 これらビジネスロジックは、サービスとして実装できるものばかりである。Entityを入力値としてサービスを実行し、データが妥当であればステータスを更新すればよい。これは、リソース志向に則った設計でもある。

 ビジネスロジックに関連して、プロジェクトでどうしてもやりたかったことで、否定の1、および10がある。最後に、これについて説明したい。

1.受注番号等のキーを採番しない
10.Web受注はブラウザからのプロバイダ通信だけで完結させる

 採番はシステムのあらゆる面でネックになるので、何とか頑張って無くしたかった。そのかわり、「誰がいつ注文したか」を示す、メアド+受注日時(takezaki@mail.com-20081012010203012)をキーとした。そして、プロジェクトの第一フェーズまでは実際にこれで運用した。

しかし、これはやはり無謀であった。・・・動くものはなんとか作れたが、データ操作が非常に面倒くさくて、とても運用できるものではなかった。大反省( ><;した末、今では受注番号はキーとして必須であるという結論に至っている。
 採番はシステムのあらゆる面でネックになるといったが、特に心配だったのはスケーラビリティへの影響である。これは採番プロバイダとしてサービス化することでなんとか解決できたと思う。現在の採番プロバイダは受注番号以外にも配送伝票番号や生産番号などのキーを複数発行している。
 また、プロバイダ通信だけで完結させるという案10は、採番プロバイダの例のように作ることで有効であるように思えた。実際にECシステムでは、商品マスタや受注機能のプロバイダ化ができており、AJAX(JavaScriptによるプロバイダ通信)を駆使すれば、ブラウザとプロバイダだけでECサイトが作れると思った。しかし、セキュリティとSEOの2つの理由で断念せざるを得なかった。まず、カードオーソリのために外部サービスを呼ぶ必要があり、これをクライアントのJavaScriptだけで実行することは不可能であったこと。それから、SEOにおいては、HTMLを出力できることが重要で、どうしても中間にXMLからHTMLを生成させるPHPを介在させる必要があった。
 このように、あまり面白くない結果になってしまったこのECサイトであるが、実はPHPから商品プロバイダや受注プロバイダを呼んでいるし、カート(Flex2)からも同様にプロバイダを呼んでいる。にもかかわらず、外部から見ると普通にDBを検索しているように見えてしまう。まあ、サービス志向でWeb2.0サイトであることを醸し出せないのはちょっと残念ではあるが、それなりに意味のある設計だとは思っている。
これからも無謀と思わずいろいろトライしていきたい。

木曜日, 6月 19, 2008

【雑記】 Firefox3 圧倒的なパフォーマンスで勝負あり このエントリーを含むはてなブックマーク


この記事は信じがたいものだったが実際に入れてみたら嘘ではないことがわかった。ひゃ、はやっ!
“史上最速”のFirefox 3が登場へ、「IE7の9.3倍速い」
クライアントプラットフォームの安定化、高速化により、今後はJavaScriptの活用がさらに進むだろう。それを見越しているのか、すべての自社製品をSaaS化しようとしているAdobeもまたすごい。まあ、Adobeに関していえば、Flashを取り込んだ時点で方向性は決まっていたのかもしれないが、アプリがネットワークに依存することのリスクも覚悟しなければならないわけで、大きな決断だったとは思う。また、デスクトップインストール型に比べ、SaaSだと機能に多くの制約が出てくる。Photoshopなどは到底、SaaSにできないだろうとは思っていた。いずれにせよ、ブラウザだけで実現するネットワークコンピュータがより、現実的になった日であることは間違いない。Adobe AIR

水曜日, 6月 18, 2008

【本日の嵌り】 SSHポートフォワードでMySQLにアクセスする このエントリーを含むはてなブックマーク



MySQLサーバポート(3306)に直接アクセスできない場合、sshでログインしてポートフォワードするとよい。

「Linuxがクライアントの場合」
ssh -N -f -L 3306:192.168.1.1:3306 work@192.168.1.1

「Windowsがクライアントの場合」
puttyでトンネル=>源ポート8081、送り先localhost:8081で追加する
L8081 localhost:8081

これで解決。

火曜日, 6月 17, 2008

【おしらせ】 暮らしのデザイン取締役を退任しました このエントリーを含むはてなブックマーク


本日付けで、暮らしのデザイン取締役を任期満了により退任致しました。
プレスでも発表しておりますが、暮らしのデザインは、エディオンからニッセンホールディングスに移ります。暮らしのデザインのサービスは継続しますので、今後も引続きご愛顧賜りますよう、宜しくお願い申し上げます。

月曜日, 6月 16, 2008

【本日の嵌り】 Tomcat common/lib/xxx.jarの内部ファイルを読みこむ このエントリーを含むはてなブックマーク


Tomcatのcommmon/lib配下にあるJarファイルの内部のリソースを読めるようにしたい。
それには、Jarファイルの場所を含むurl(以下jarurl)を作成する必要がある。
jarurlは、「jar:file:/C:/・・/common/lib/xxx.jar!/jp/reflexworks/sample.xml」という感じで、!の前がJarファイル名、後がパッケージ名となる。「.」は「/」に置き換わる。
これさえ作れれば、あとはnew URL(jarurl).openStream();とやれば読み込める。

Jarファイルの場所付きurlを作成するには、以下のように、log4jのLoader.getResource()を使うと便利。


URL url = Loader.getResource("jp/reflexworks/sample.xml");
String jarurl = "jar:file:/"+url.getFile().substring(5);



これで解決。

<追記>
以下のfile://のように、2回//があると読めなくなるので、ちょっと修正。

jar:file://C:/・・/common/lib/xxx.jar!/jp/reflexworks/sample.xml


URL url = Loader.getResource("jp/reflexworks/sample.xml");
String jarurl = "jar:"+url.getFile();




土曜日, 6月 14, 2008

【雑記】 ソフトバンクがiPhoneを獲得した理由 このエントリーを含むはてなブックマーク



これは分かりやすい ↓

、iPhone 3GでYahooにアクセスし、Yahoo!オークションやYahoo!ショッピングを利用してもらえれば結果として収益は上がる。すなわちiPhoneを携帯電話ではなくインターネット端末だと考えれば、ソフトバンクにとっては十分魅力的な製品である。これはインターネット全体でビジネスをしている同社の大きな強みだ。


ソフトバンクとiPhoneが日本の携帯を変える--ソフトバンクのiPhone獲得を考える

なるほど・・・。よく考えてみたら、ソフトバンクモバイルは携帯キャリアのひとつではあるが、ソフトバンク本体は通信サービスが本業ではないのだ。全社的な取り組みとしては、通信サービス、すなわち、電波や通信回線の利用料を徴収するビジネスモデルに拘らず、より付加価値の高いインターネットビジネスに誘導する。こんなことは、到底、Docomoには真似できないだろう。

しかし、本業のインターネットビジネスといっても、Yahooぐらいしか思いつかない。ソフトバンクオリジナルは何もないわけで・・・、昔からそうなんだけど、なんとなく気持ち悪い。


水曜日, 6月 11, 2008

【本日の嵌り】 Google先生にエラーコードで聞く このエントリーを含むはてなブックマーク


ちょっと恥ずかしいが毎度のごとく嵌るネタをメモしていくことにする。
Googleにエラーコードそのものを入れて検索するとズバリ回答が出てくる。これは便利だ。

嵌り1)昨日、久しぶりにTomcat+Eclipse環境を使ったのだが、Tomcatプラグインがちゃんと動かない。

java.lang.ClassNotFoundException: org.apache.catalina.loader.DevLoader

と出ているので、Google先生に「org.apache.catalina.loader.DevLoader」と入れて聞いてみた。そしたら、件名:EclipseでTomcatが動かない!!が見つかって、

プロジェクトのプロパティでTomcatを選択すると、
開発用クラスローダの設定がありますが、
「使用する」にチェックされていませんかね?
チェックされていたら、チェックを外してみてください。


それで無事Tomcatは動くようになったが、今度はアプリケーションが見つからないようだ。
maven2の環境だったので、server.xmlのContext docBaseに以下を追加

docBase="・・・・\src\main\webapp"

これで解決

嵌り2)Maven2でデプロイしようとしたら 「\65279 は不正な文字です」と出る。
エディタで見ても先頭には何も文字らしきものは入っていない。そこで、Google先生にコピペして聞いてみたら、\65279 は不正な文字です。が出てきた。
サクラエディタで開いても見れないが、ファイル->名前を付けて保存 とすると右下にBOMがチェックされているので、これを外すとうまくいった。

これで解決


木曜日, 6月 05, 2008

【雑記】 iPhoneは売れる? このエントリーを含むはてなブックマーク



この2つの記事を読んで売れるか売れないか考えてみた。

iPhoneがなぜそれほどまでに「革命的」なのか
iPhoneは売れない

見事なほど対照的な意見だ。

最近のAppleはたしかにすごい。何年か前に、MSから資本が入ったときは、「生かさず殺さず」などといわれたAppleが、よくもここまで盛り返したものだと思う。ソフトウェア以外にコアテクノロジーがないにもかかわらず、何かすごいものをもっているように感じさせるのはなぜだろう。「おもてなし」とか、後付けの理由はいろいろあるようだが、私の理由は、MSとSONYの自滅だ。その結果、相対的にブランド力が向上しただけじゃなかろうか。「おもてなし」と言い切ってしまうと、私のような鈍いものにとって説明が難しいと思う。(私はiPod nanoの掘り込み印字に気がつかないし、スタバに入ってもドトールと区別がつかない)

 かつてのソニーは技術力によりブランドイメージが作られた。トリニトロンとか、ウォークマンとか、ソニーの独自の技術力は、他社に追随を許さないように思えた。当時はCMもすばらしかったので購買意欲は増幅される。そして、その商品を手に入れたユーザの満足感は非常に満たされた。これがすなわち「おもてなし」。このような好循環になれば多少高くても買ってくれる。ブランド力が向上した結果、安いネタで高く売るというビジネスモデルを確立できる。

 また、MSはWin95,Win2000に尽きる。まるで一人の人間が作ったかのような、統一された出来栄えには非常に驚かされた。一方のOS2は、人が寄ってたかって作ったんだろうなという感じを受けた。OS2には操作に統一感がないので、使うときに「あれ?どうやるんだっけ?」と迷うことがよくあって、私はすぐ忘れるのでメモに残すことをしていた。一方のWin95は、たぶんこうやればできると直感的にすぐわかった。メモはもちろん必要ない。当時の北城社長は95を「ゲームのような、おもちゃOS」と酷評していたが、実は「おもてなしOS」だったというオチ。MSのすごいところは、たぶんこうやればできるという直感的な操作感を、Officeや、さらにはSQLServerにも継承させていること。MSのソフトであれば、いちいち操作を覚える必要はないと想像できた。これもブランド力ではなかろうか。
 (カミングアウト)まだ新人であった私は、非力なPCしか与えられなかったことを理由に95を社内で堂々と使っていた。(北城さんが何ていおうと、「だって僕のマシンではOS2動きませんから」と言い訳して、内心ニヤリ)同期で95ばかり使っていた社員は、たぶん私だけだったろうし、最もOS2が分からない自信があった。それから社内の冷たい視線に絶えること数年、OS2は使われなくなった。

最近は、SONY、MSに陰りが出てきた。一方、追いついたAppleが成長した。Macは私もたしかに欲しいが、その一番の理由はVistaを使いたくないから。現時点で最高なOSはもちろんWin2000。軽くて早くて使いやすい。たぶん、Macに乗り換えるのは時間の問題だろうが、ThinkPad X21のキーボードに慣れた私が乗り換えたときに苦労するのは明らかなのでまだ買っていない。なので、私はいまだにIBM社販で買ったThinkPad X21を愛用している。(2000年に社販購入して償却したところで退社した。)

話は戻る。
iPhone VS ワンセグお財布携帯、どっちが勝つのだろう?

今回の勝負は、「技術力=ワンセグお財布」 VS 「おもてなし=ブランド+使いやすさ」だ。

でも、こんなふうに考えるとぜんぜん面白くないなあ。

私の予想はズバリ、今年に限って言えば、話題性はあるが大ヒット商品にはならない。少なくとも、今年年末のヒット商品の横綱にはならないだろう。でも。iPhoneの背景にある「革命的」なことを考えると、通信業界にとって「大インパクト商品」なんだろうなという気はする。「革命」の流れは止められないと思うので長期的に見るとiPhoneの勝ちだろう。通信業界の変な構造は変革させたい。ガラパゴス化がすすんでいる日本の技術。それを駆逐する「革命的」な技術という意味で、DOS/VがPC98を駆逐していった状況に似ていると思う。

iPhoneの背景にある「革命的」なことについては、こちらの本を参照してほしい。



最後に、電池の寿命は重要。W-ZERO3は電池がすぐ切れるから痛かった。

iPhone とおサイフケータイ

火曜日, 6月 03, 2008

【EC開発体験記 -Model-】 2面性をもった掴み所がないオブジェクト このエントリーを含むはてなブックマーク


以前、Modelとはモデリングの成果物であるという説明をした。分かりにくかったかもしれないが、これは要するに「オブジェクト」であり、データおよび、それを操作するメソッドを含んだ「もの」ということだ。オブジェクト志向で何が嬉しいかというと、詳しくは、WEB+DB vol44を見ていただきたいが、一言でいうなら、

オブジェクト志向では、「もの」によって「できること(機能)」が連想できるのです。
                       - WEB+DB vol44 p76 豆蔵 萩本氏 -



普通に作るとModel=Entity+Blogic(業務ロジック)となる。「もの」が持つデータはEntity、「できること(機能)」がBlogicである。これはこれで問題ないように思えるのだが、実際にBlogicを実装していくと多くの問題に突き当たる。その原因の一つが、自分以外のモデルを参照する外部参照である。外部のModelは参照するけれども、主な責務は自分にあるので内部にメソッドを定義している、といったケースはよくある。しかし、そのメソッドが返す値は自分自身のEntityとは関係なかったりする。こうなってくると何でもアリになってくる。そして、「Entity重要」から「Blogic重要」になり、しまいには「あれ?Modelって何だっけ?」という話になる。これではモデリングの意味がないし「Entity重要」を強調するためにも、一旦、Blogicのことは忘れてEntityだけを設計することに注力すべきである。これがオブジェクト設計といえないことは前述したとおりである。なお、Blogicの解決策については、後半部分で触れる(太字で記す)

ここで、「Entity重要」の設計について整理してみたい。

1.分析クラスのEntityをもとにデータの構造体を実装する(entity0)
2.entity0のCRUDを実装する


問題は外部参照だ。Entityは他のEntityとコミュニケートできないとされるので、うかつに関係線を引くとモデラーの先生に叱られることになる。であるなら、関係のない新たなEntityを作成すればよい。(ただし外部参照のEntityへは内包線が引かれることになるので注意する)



Entityを内包するためにControlを使う。

Controlの定義


1.CRUDメソッドの実装部分を受け持つ
2.Blogicや外部参照のEntityを複数呼べる
3.トランザクションを管理する(1UOW)
4.Model内からのみ使用できるメソッドである(private) (修正:2008/09/30)



具体的には、次のような、controlXを追加する形になる。


1.分析クラスのEntityをもとにデータの構造体を実装する(entityX)
2.外部のEntity(entity0,entity1)を参照して内部のEntity(entityX)にマッピングする(controlX)
3.entityXのCRUDを実装する
(省略)


サンプル


 // Entityクラス
 public class EntityX extends ControlX{

  public String item0; // 要素0
  public String item1; // 要素1

 }

 // Controlクラス
 public class ControlX {

    private Blogic blogic = new Blogic(); // Blogicクラス(業務ロジック)
    public EntityX retrieve(EntityX entityX) {

        Entity0 entity0 = (new Entity0()).retrieve(entityX.item0);
        Entity1 entity1 = (new Entity1()).retrieve(entityX.item1);

        entityX = blogic.convert(entity0,entity1); // マッピング

        return entityX;
    }

    public EntityX create(EntityX entityX) {
       ・・・
    }

    public EntityX update(EntityX entityX) {
       ・・・
    }

    public EntityX delete(EntityX entityX) {
       ・・・
    }
 }




また、例のように、外部参照のEntityは複数あっても構わないが、内部EntityのCRUDは1UOWとする必要がある。すなわち、複数の外部参照のEntityをトランザクションで括る必要がある。(この実装はちょっと難しい。内部APIのような密結合であれば、実質的にはSQLになるのでDBMSのトランザクション管理を利用すればよいが、サービスのような疎結合では2phase commitなどを考慮しないとうまく管理できない。いずれにしても、この実装では、ロングトランザクションではなく、Atomicなトランザクション管理が要求されることになる。)

また、convertというBlogicが登場している。これはentity0,1からentityXにマッピングするメソッドであり、entityXのModelに属するものだ。さらにいえば、controlXもentityXのModelに属する。Entityの中にControlが入ることになるが、これも内包として考える。(内包は再帰的に考える)
 そして、convertに限らず、BlogicはすべてEntityを入れてEntityを返すとしてよさそうである。ただし、BlogicにI/Oを持たせてしまうと外部参照となってしまうため基本的にNGである。ResourceへのアクセスはEntityのCRUDメソッドだけが許されるものとし、さらにBlogicからはそれを呼ぶこともできないとする。EntityのCRUDメソッドを呼べるのはControlからだけだ。
Blogicで可能なことは、Entityを元に計算や変換処理を行って、Entityに詰めなおして返すだけ。
なんとも制約の多いものだが、これがカプセル化として働くため、非常に安定したシステムとなる。(後ほど説明するがテストも楽になる)
したがって、Blogicの定義は次のようにする。


1.entity = blogic(entity)
2.Blogicは揮発性である(ステートレスでありデータを保持しない。また、外部リソースへのI/Oはない)
3.Model内からのみ使用できるメソッドである(private) (修正:2008/09/30)



このように、「Entity重要」の設計を行っていくと、自然とBlogicが整理されてくる。
Blogicが追加されて最終的に出来上がったものがModelである。そして、そのModelがさらに他の参照元になりえるという再帰的なModelとなる。(Groovyコンファレンスデモの実装の例を記事に書いているので参考にしていただきたい。=>リソースの実装)

Controlは、Modelの中に内包されてEntityとしても振舞う。考え方としてMashupに近い。


以上が私の考える「Entity重要」のリソース志向設計である。
独自色が強いのでおすすめはあまりできないが、何か設計の参考になれば幸いである。

月曜日, 6月 02, 2008

【EC開発体験記 -スキーマ開発-】 誂えスキーマを開発してはいけない理由 このエントリーを含むはてなブックマーク


サービスのAPIを開発しない理由にもつながる話。

自身、TravalXMLとか、いくつかの誂えスキーマに携わったことがあるにもかかわらず、否定するのは筋が通らないかもしれないが、無駄なものは無駄である。まず、以下を読んでいただきたい。

お仕着せスキーマと誂えスキーマ

そして、Tim BrayのBigFiveは、それを強調した話となる。
Tim Bray 「もうXML言語を開発するな」 と、それに対するUche Ogbujiの反応

これらを読むと、XMLではないが、APIを(標準化を見据えて)開発することは無駄なことのように思えてくる。

では何が「お仕着せ」で何が「誂え」なのか。
産業界で普及しているという尺度には議論の余地があるものの重要な判断材料だとは思う。しかし、AtomPubといえども「お仕着せ」に見えて、結果的に不発で終わるかもしれない。
こうなってくると余計、分からなくなってくる。

一方、WikiPediaのタグ化に見られるような語彙は、地味ではあるが、着実に浸透すると思われる。
FavikiとタグとDBpedia

結論は、ユーザが開発するスキーマは野良で、それから、できればWikiPediaのタグを使おうということでOK?

土曜日, 5月 31, 2008

【EC開発体験記 -リソース志向-】 APIをやめデータ構造に着目する このエントリーを含むはてなブックマーク


あらかじめ断っておくが、ここで説明するリソース志向は、私自身の開発の経験から必然的にたどり着いた解であって、一般的に説明されているROAとは多くの部分で異なる。ここでは、正確なROAの説明をするつもりはないし、本当のROAとは?などと、不毛な議論もやりたくない。ROAもしくはリソース志向という言葉を便宜上使わせていただいているが、それは、自分の解に一番近いと思われるからである。したがって、私の説明をそのまま世間で使うと痛い思いをするかもしれないので気をつけていただきたい。

まず、リソース(以下Resource)の定義から説明したい。Resourceとは、分析クラスから導き出されるEntityを具現化したものである。噛み砕いていうと、モデリングにより定義された自己完結したデータの集合という感じだ。前記事で、分析クラスのBoundary、Control、EntityをStrutsのMVCモデルに落とせない話をしたが、リソース志向では、EntityがそのままResourceとなる。これは非常に分かりやすい。(Entity設計の責務は誰が負うべきかという課題はあるが、これはまたの機会に説明したい)また、Resourceは、それを指し示すID(URI)をもち、データ構造を公開できる。公開の手段は、XML、JSON、YAMLなんでもいい。これについては、インターオペラビリティの話として後ほど詳しく説明する。それから、ResourceのAPIはCRUDの4つだけとする。トランザクションを管理し、それぞれのAPIは1つのUOW(UnitOfWork)で実行されなければならない。注意していただきたいのは、このAPIはHTTPに限った話ではないということ。言語のAPIにもなりえる概念的なものということだ。また、RESTはステートレスといわれるが私はそうする必要はないと考えている。単純にステートレス=揮発性と考えると、リソースが揮発性になってしまうのはむしろ不都合になるからだ。リソースは不揮発性だが、トランザクション管理という意味では、不完全なものは破棄するという性質(揮発性)は持たなければならない。ステートレスの話はトランザクション管理に帰結すると思うので、私の定義では1UOWとなった。

<私が考えるリソース志向>

1)リソースは自己完結したデータの集合
2)リソースはデータ構造をもち公開できる
3)リソースに対してCRUDで操作する
4)リソースは特定できるアドレス(URI)をもつ
5)リソースに対して1UOWで操作する

ここで話が戻るが、公開するデータ構造は何でもいいという話をしたい。公開用のスキーマ言語は、必要最低限のデータ構造を表現できれば十分。それは、きちんとインターフェースを定義したい方はスキーマ言語を使うだろうが、それでもセマンティクスを十分に定義できないのは同じであり、いずれにしてもデータ長や意味などを説明した資料は必要になるからだ。
 私はかつて、Webサービスの最も重要な性質の一つとして、インターオペラビリティを挙げていた。しかし多くの経験から学んで今ではかなり否定的になっている。数年前になるが、XMLコンソーシアムで、参加企業十数社がそれぞれの製品をもちよってWebサービスの接続実験を行ったことがある。その実験では、たしかに、言語やプラットフォームを越えて接続できるという利点は認められたが、各製品の制約が多すぎて、WSDLを変更しなければならないことがしばしばあった。製品に合わせてWSDLを変更するなんて本末転倒である。実際に接続するには相当な苦労があったわけで、ダイナミックバインディングなんてのは夢の世界。絵に描いた餅であった。そもそも、スキーマ言語でセマンティクスを完全に定義できないことの重要性に早く気づくべきだった。
 インターオペラビリティの問題は本当に難しい。WSDL(XMLSchema)はCORBAにはできなかった言語の壁を越えることができたがセマンティクスの問題は解決できなかった。AtomPubは、この問題を解決できる可能性があるとは思ったが、普及していない現状を見る限りでは難しいように思える。(AtomPubにはすごく興味があって、ReflexではRFC前であるにもかかわらず実装を行った。たしか、2005年頃までのdraft-ietf-atompub-protocol-04.txtの実装を行ったが、自分の感性に合わなくなったことと、インターオペラビリティに疑問を感じたのでやめてしまった。)結局のところ、AtomPubにも同様の困難が待ち受けていると思われる。

そこで得た結論は、否定の3と7

3.各サブシステムで共通のエンティティを使用(最小単位は1品1葉1レコード)
7.サブシステム間を密に結合させない(サービスだけで繋ぐ)

これは要するに、システム共通のentityを定義して、それをやりとりすることだけに注力しようということだ。SOAPやAtomPubなどのAPIを介さないし、そのような共通APIに該当するものを敢えて開発しない。しかし、最低限は必要になるので、先に挙げたリソース志向を踏まえて定義する。具体的には次のような非常にシンプルなAPIを定義することになる。

<API例(entityはリソースの1インスタンス)>

entity = create(entity);
entity = retrieve(entity);
entity = update(entity);
entity = delete(entity);

サブシステム内であれば上記APIをそのまま使う。サブシステム外ではHTTPのPOST/GET/PUT/DELETEがそれぞれ対応する。例えば、GETを実行するとシリアライズされたentityが返ってくる。繰り返しになるが、entityの表現はJSONでもXMLでも何でもいい。実際のプロジェクトではReflexによりJSONとXMLの両方を使っている。(Reflexはentityの写像を取るフレームワーク。entityをJSONとXMLにシリアライズできる。)

自己完結したデータの集合が意味するところは、一つは自分から他のリソースへの依存がないということ、もう一つは、外からの影響を受けないこと。例えば、他リソースのインターフェースが変更になっても、スケルトンを取り込みなおす必要がない。
 サービス志向では、インターオペラビリティの問題があって、セマンティクスに関係した部分が不安要素として残っていた。自己完結したデータの集合、すなわち、リソースを中心に設計していくことで、より安定した大規模なシステムを並行開発できるようになる。


日曜日, 5月 25, 2008

【Hadoop】 究極のWebサービス。やられた感いっぱい ><。 このエントリーを含むはてなブックマーク


AWSとHadoopでiText変換はまさに考えていたところ。ショック大きい><;。
でもこれがWebサービスの究極の姿であり、Virtual Technologyが目指すところ。めげずに頑張ろう・・・。
Self-service, Prorated Super Computing Fun!
解説
Running Hadoop MapReduce on Amazon EC2 and Amazon S3

Hadoop MapReduce
Hadoopアーキテクチャ
Hadoopクイックスタート日本語訳
Hadoopを触る
Hbaseアーキテクチャ
Hadoopいろいろ
Hadoop HDFSのRead/Writeサンプル
それからIBMもHadoopに注目していることが分かった(前記事の発言は撤回しますm(_ _)m)
IBMのBlue Cloudは、ウェブコンピューティングの別名

金曜日, 5月 23, 2008

【EC開発体験記-設計-】 MVCモデルは進化する このエントリーを含むはてなブックマーク



アーキテクチャスタイルと重きを置いているレイヤを見ていただきたい。これは2年ぐらい前の絵なので、ちょっと古いし、正直、適切でない言葉が使われていると思う。RESTはSOAと同列に書くとすればROAだろう。SOAなんてカッコつけないで、Webサービスぐらいにしておけばよかった。また、Web2.0をBoundaryに置いているが、広範囲に使われる言葉になった今では違和感がある。当時はAJAXブームもあり、クライアント技術が先行していたので、それほど違和感なく見れたのだが、それにしても、この業界の進化は早い。今これを書き直すとすれば、View、Mashup、Resource、といったところかな。実装技術では、Viewのところが、JavaScript, Flex2 AIR, PHPで、Mashupのところが、PIPES, Plagger, sMash、Resourceのところが、Hadoopやhbase, ネット上のRSS,ATOMとするのがしっくりくる。Reflexはどのレイヤにも当てはまるがResourceに置くのが一番適していると思う。(書き直した絵=>RESTful設計におけるレイヤと主な実装技術
 長々とわけ分からん絵の説明をしたが、いいたいことは設計をどうすべきかということ。
 オブジェクト指向設計では、まず、ユースケースの図と記述を作成して分析クラスを導き出す。プロジェクトでも同様にやったのだが、これは非常に有効な手法であった。しかし、分析クラスから、その後の設計クラスをどのように作っていくかについては、モデラーの感性に依存するというか、正直なところ曖昧で、あまり機能していないのではないかと感じている。世の中にはこれを課題と感じないスーパーなモデラーがいるのは承知している。分析クラスをそのままStrutsのMVCモデルに落とし込めないのが分かっていても、彼らは平気な顔して設計クラスをサクサク作っていける。(もしかしたら彼らには分析クラスは必要ないのかもしれない)あるいは、経験を積んだモデラーにとっては(※)モデリングがパターン化されており、単純な工程で済んでいるのかもしれない。
(※ オブジェクトを設計していくことをモデリングという)

分析クラスのBoundary、Control、EntityがそのままStrutsのMVCモデルに落とせない、なんていいだすと、スーパーモデラーじゃなくても異論が噴出するかもしれない。(面倒なので詳細は省略するが)単純にいうと、分析クラスのBoundaryは主に画面(入出力装置)、Entityはデータ、Controlは要求に応じてデータを集めたり加工して返すところとなる。問題は、Strutsに落とし込んだときに、Controlのおき場所に困ることだ。StrutsにおけるControlerはフレームワーク自身を指す(あるいは含んでいる)ので、アプリケーションのControlerの立場はどうするの?といった話になる。多くはModelに集約されることが多いようだが、これはこれで面白くない。なぜなら、せっかく分析クラスで分けたものを設計クラスで一緒にするなんてモデリングの意味ないじゃん、と思うからである。稀に、Controlerのパッケージを作って、きっちり別管理しているプロジェクトもあるようだが、管理に強制力はないため、プログラマーによってグチャグチャにされてしまい、どれがModelでどれがControlerか区別がつかなくなるのが現状のようだ。まあ、ControlerとModelをきっちり分けることで、いいことがあるとすれば、ワークフローを導入してControlerを外だしにするような場合だろう。そんな話は滅多に聞かないから、グチャグチャになっても、それほど大きな問題はないと思われるが・・。
いずれにせよ、Modelの実装はけっこう面倒で工数がかかる。テーブルはRDBの世界であり、一方のEntityはObjectの世界なのでインピーダンスミスマッチを解消するためO/Rマッピングが必要となる。私はこのO/Rマッピングが工数増大の大きな要因と見ている。オブジェクト設計のドメインモデルでは、Entityからテーブルを作成していくのが基本だが、テーブルは非常に重要な部分なので、人によってはテーブル設計から先に始めるかもしれない。(分析クラスがなくてもモデラーが平気な顔して設計できるのはテーブル設計があるからだろう?)しかし、ボトムアップ的にそうやってしまうと、Rの都合により作成されたEntityクラスとなって、モデリングが無視されたオブジェクトになる。そのままだと今度はControlerやViewから使用できなくなるので、O/Rマッピングしたクラスをさらに組立て直して、Modelの形を崩さないよう努力するはめになる。そのような組立直しクラスをDXOと呼ぶそうだが、そんな無駄な処理をモデルの中でゴニョゴニョやっているのである。これはもはやオブジェクト設計ではない。

 そこで、得た結論は、
否定の6.オブジェクト設計をしない

である。正確には、設計クラスのModelをオブジェクトにはしないで、オブジェクトで扱うデータに関心を集めようというもの。Modelとは、モデリングによってできた成果物(※)でありオブジェクトであるから、データとプログラムがくっついている状態。それからプログラムを切り離してデータのみに関心を集めて考える。データだけであればオブジェクトではないのでオブジェクト設計をしないことを意味する。とはいえ、分析クラスまでは設計して論理ビューを作る。論理ビューで定義したサブシステム内であれば、勝手に設計/実装して構わないという感じになる。具体的な実装例の記事を書いているのでこちらを参照願いたい。

ZEROからRESTfulアプリを作成
ビューの実装
リソースの実装
フローの実装

(※ Modelの定義は曖昧で様々な説明が存在する。私はこの説明が一番好きだ。)

しかし、これでも同様にO/Rマッピング問題は存在する。Entityは本来はオブジェクトの視点で設計するが、実際にデータを格納するテーブルの設計とどう整合をとるか。これは次の2点で解決する。要は、設定よりも規約(Convention over Configuration,CoC)をやろうということだ。参考:Entity設計

1.Entityはデータ構造の集まりであるから、これをそのままRDBにマッピングする(Reflexはその機能をもっている)
2.ControlやViewからEntityへのアクセスはCRUDのみとする

Entityの設計では、まず、データ構造を定義して単純なAPIを公開する。公開の手段は、XMLやJSONなどを使って表現する。また、APIはデータ操作だけなのでCRUDのみとする。HTTPであれば、GET/POST/PUT/DELETEの4つだけだ。これはRESTと呼ばれるアーキテクチャスタイルだ。Webサービスであればサービスごとに定義されたインターフェースとなるところを、RESTでは4つのAPIでリソースを操作することになる。

データのみに関心を集めたEntityでは構造化はされているがRのイメージに近い。
テーブルのカラム名がクラスのフィールド名と同一となり、それをシリアライズした、JSON、XMLのタグ名も同じになる。テーブルは、O/Rマッパで隠蔽されているイメージから開放され、語彙が統一できてシステム全体で見通しがよくなる。

あと、忘れてならないのはビジネスロジックの存在だ。これは今までModelの中に実装されていたが、Modelはデータだけになったので、別途管理する必要が出てくる。また、実行時にModelをパラメータとして渡すことになるが、まあ、これはこれでテストが楽になるのでOKかなと思っている。

<関連>
RESTfulな新3層アーキテクチャ
ドメインモデル貧血症、サービスと振る舞いについて
3分で分かる設計の話

水曜日, 5月 21, 2008

【雑記】 何やってんだ? このエントリーを含むはてなブックマーク


最近オープンしたgeohistサービスは、地図と携帯のマッシュアップブログだ。作者は、あの横井勝商店。目的はいまいち不明だが発想は面白い。中をのぞくと横井さんの行動が全部見れる。そのなかで、もっと面白いものを見つけた。なんと、geohistのティッシュを新宿で配っているではないか。

「ユニクロの袋でティッシュ配りはまずいか」だってさ。心配なのはユニクロの袋じゃないだろ?

腹がよじれるかと思った。

<追記>
geohistとGeohashing 座標とハッシュ値でオフ会を組み合わせると超クレイジーなイベントができるかも。集まるまでの途中報告をgeohostで行うことにすればいい。

火曜日, 5月 20, 2008

【JUI Tokyo】 JavaScriptいろいろ、個人的にOpenID このエントリーを含むはてなブックマーク




昨日、JUI Tokyoに行ってきた。jQueryのPaul Bakausさんによるプレゼンでは、新機能のデモが印象的だった。次々にインパクトのあるデモが紹介されたがソースは非常に短い。ほとんどがプレゼン1画面に収まっていた。Q/Aでは、経歴とか、チーム開発の困難さとか、いろいろ。彼はJavaScript暦5年だそうだ。チーム開発はあまり困難を感じてないようだった。フルタイムは僕一人だからといっていた。JavaScriptの大規模な開発は私も興味があるところだが、ちょっと拍子抜けした感じ。jQueryの今後に期待したい。その他は、amachanやma.laさんの高速化の話が参考になった。今困っているところなので、こういう話は非常に助かる。

下は懇談会の様子。右からshinichitomitaさん、ma.laさん、amachangさん。

amachangさんは私の困っている話を「ウン、ウン」と聞いてくれたが、ma.laさんは半分あっちの方を向いて聞きたくなさそうだった。でも、「表の項目全部にイベントハンドラーを登録している」というようなことをいったら、「そりゃいかんだろう。数百、数千になると動かなくなるよ。1つで十分」とおもいっきり反応してくれた。(onclickを使え!と返されると思ったのだが本音は違うみたい・・?)その後、shinichitomitaさんがやってきてOpenIDの話をした。OpenIDはなんで流行らないか。特にRP側(*)で流行らない理由がわからないというので、一応、RPを導入する可能性のある立場でもある私の気持ちを率直に伝えた。一番の理由は、必要性を感じていないということだ。がんばって導入したところで効果が今の段階ではよく見えない。あえて挙げるとすれば、ユーザビリティの向上が図れた結果、以下のような効果があることぐらい。

 1) 既存OpenID会員など多くのお客様を呼びこめる。
 2) 機会損失を小さくできるのでリピータを増やせる 。

ちょっと買ってみようと思った「一元さん」は、会員登録とかいろいろ面倒だからすぐに逃げてしまう。たしかに簡単に買える仕組みは必要だろう。でもよく考えてみたら、ECサイトではお客様に商品をお届けしなきゃいけないわけで、最低限、名前と住所などの個人情報の登録は必須となる。(補足すると、OpenIDでできるのは本人確認のみ。例えば、takezakiというIDでログインしていれば、確かにtakezakiだということを確認できるが、それ以上のことはない。本名もわからなければ住所も分からない。また認可は別の仕組みが必要となる)
 こう考えるとなおさらOpenIDの導入は余計なことのように思えてくる。そもそも、OP(*)がRPに個人情報を渡すとは思えないので、RP自身で個人情報を集めなければならない。ユーザビリティの向上は何もOpenID使わなくてもできるし、機会損失だって本当のところどうだかわからない。結局、キラーアプリケーションでも出てこない限り、OpenIDはあまり流行らないんじゃないかなあ、なんてことを懇談会では話した。

 ちょっと宣伝になるが、ここでは、「一元さん」のためにメールアドレスだけで買えるような工夫をしている。お届け先はメールに飛んできたリンクをクリックして登録すればよい。最低限必要な本人確認および、お届け先入力をこのように実現することで、ユーザビリティ向上を図っている。また、メール注文サービスというのもやっていて、k@g.edionjpにカタログの番号をメールするだけで商品が買えるようにもなっている。原理は一元さん購入と同じだ。

 でも今、いいアイデアを思いついた。ポイントをOPで管理するというのはどうだろう。

1)OPでポイントを管理する(RPで購入するとOPのポイントが増える)
2)すべてのRPでポイントを使用できる

ポイントはお金と同じように扱われるから、ECだけじゃなく、いろいろ利用するケースはあるかも。SuicaとかEdyとかに変換できると嬉しい。使えるところが増えれば増えるほどポイントの価値は高くなるので、それであれば自社ポイントはやめてでも導入したい。
それから、すべてのRPの商品情報がOPで検索できるといいなあ。

(*) OPとは. OpenID Providerの事。
RPとは. Relying Partyの事。(要はリクエスタ)

月曜日, 5月 19, 2008

【雑記】 CAPTCHAの話 このエントリーを含むはてなブックマーク


CAPTCHA絵画 <=これ、CAPTCHAが芸術になるなんて面白い話だ。

CAPTCHAは文字などを人間しか認識できないような絵にする。これを逆に機械が読み取って文字にするのはとても難しいという。完全に破るプログラムは人工知能の一つといえるかもしれない。人口知能の定義はたしか「端末越しにやりとりしたときに人か機械か見分けがつかないもの」だったと思う。

面白い話をもうひとつ。アダルトサイトに仕掛けて多くの人にログインを促して裏ではCHAPHA破りに使うという技術がある。破られる側からしたら、あたかも人工知能を使われてるように見えるだろう。これを「エログリッドコンピューティング」というらしい。その命名が笑える。

[ロ]CAPTCHA と「エログリッドコンピューティング」

土曜日, 5月 17, 2008

【EC開発体験記-サービス志向-】 疎結合で真価を発揮 このエントリーを含むはてなブックマーク


以下は「Webサービスによる非集中化プログラミングモデル」、懐かしいけどちょっと恥ずかしい自分の論文(ProVision掲載 2000年)からの引用。

 非集中化プログラミングモデルでは、サービス提供者の責任範囲を、インターフェース定義に従って「あるリクエストに対して処理する、あるいはメッセージとしてレスポンスを返す」ことだけに限定しており、サービス提供者は、全体がどのようなアプリケーションで、どのように利用されているかを関知しません。このように、責任が明確になることで独立性が高まり、また、管理の隠蔽という性質から、プログラム変更などでシステム全体に及ぼす影響を最小限にできます。


今のSOAのエバンジェリストに読ませたら鼻で笑われそうな内容だが、当時はこれでも脚光を浴びた(と本人は思っている)わざわざ自分の論文を引用したのは、それを自慢したいからではなく、1年かけて実際にECサイトを構築し、サービス志向設計を経験していかに重要で効果的かを知ることができたこと、また、SOAを活用したすばらしいシステムであることを自慢したいだけなのだ。(結局自慢かよ・・・)

否定の7
「7.サブシステム間を密に結合させない(サービスだけで繋ぐ)」


言語の記事でも触れたが、私たちのECシステムでは、PHPとJavaの両方を使っている。PHPが主にリクエスター、Javaが主にプロバイダーだ。実は、PHPの開発者はJavaを知らないし、Javaの開発者はPHPを知らない。共通スキルはMySQLとLinuxだけだ。一昔前であれば考えられなかったチーム編成なんじゃなかろうか。お互いを干渉しない。干渉しようと思ってもできない。結果、自然と自分の責任範囲に集中することになる。これで本当の意味でサブシステム化が可能となる。私はサブシステム化する目的の一つは並行開発にあると考えている。密結合では実質的に無理な状況であった並行開発を、サービス志向であれば可能にできる。「完全なる隠蔽」によりシステム全体に及ぼす影響を最小限にできるため、分業・並行開発ができるようになるのだ。実際にはそうなっていないStrutsプロジェクトを見ると哀れみの念さえ憶える。(言い過ぎか・・)ただし、何から何まですべてサービス化するのは冗長になりすぎるのでやめた方がよい。うまく設計しないと、パフォーマンスが悪くなったり、不安定になったりする場合があるので注意が必要だ。システムはシンプルに作るのが一番である。お金をかけた分、それに見合うコード量を要求する客はまずいないし、シンプルでかつ品質が良ければ結構なことだ。なので、必要ないところは極力サービス化しないようにすべきだろう。おすすめは、サブシステム間の接続点でのみ使用すること。また、入力後のデータ参照は自由に行ってよいが、入力の時点では、あらゆる「狂ったデータ」にも耐えられるように厳しく設計すべきだろう。それに、入力の接続点は唯一にした方がいい。

今回のシステムでいうと、受注サブシステムへの入力は、インターネットサイトからのWeb受注以外に、コールセンターにおける電話受注がある。電話受注ではPHP画面から受注テーブルに直接SQLのINSERTを実行しており、一方のWeb受注ではサービスプロバイダー経由で受注テーブルに入るようにしている。このようになったのはインターネットサイトを電話受注システムより後に構築したからだ。結果的に入力における接続点は2つ存在することになったのだが、これが大きな不幸を招く結果となった。テーブル設計書や仕様書レベルにはない細かい違いが災いして、入力経路が異なる受注で「狂ったデータ」のような振る舞いを起こすデータがしばしば見られるようになったのだ。このように、2つの接続点があるのは管理上もよろしくないので、ここは思い切って受注プロバイダー1本に統一すべきであった。そうすればデータの信頼性も高くなる。

お気づきかもしれないが、サービスの接続点は厳しくした方がよい、というのは前記事と矛盾することをいっている。わけわからんようになってしまったが、それだけ前記事はチャレンジングであるということで、ご容赦願いただきたい。

<追記>
 ちょっと前になるかもしれないが、サービスのインターフェースを厳密にしようという考え方があった。すべてのサービスやそのクローンはWSDLだけで作れるし、それに従えば自由に接続できると考えた。(ダイナミックバインディング)しかし、実際には予測できない「狂ったデータ」に対応できないので、うまくいかないことの方が多い。あらゆる可能性を考慮して厳密に作ろうとするのはそもそも無理がある。今回の2つの接続点の件がいい例だし、前記事のいわんとしているところもここにある。
なので、今の私のなかではエンティティに着目する考え方に大きく傾いている。どういうことかというと、サービスをエンティティとビジネスロジックに分離して、エンティティをプロバイドするという考え方だ。これはRESTに近い。近いうちに詳細は説明するつもりだ。

木曜日, 5月 15, 2008

【EC開発体験記-スケーラビリティ-】 信頼性とジレンマ このエントリーを含むはてなブックマーク


前記事で、トランザクション処理は厳密にしないといいながら、でも「これはこれで問題だ」と書いた。厳密にしないとエンバグになることは前述した通りだし、結果的にデータの信頼性が損なわれるので問題と書いた。しかし、あながちそうでもないらしい。ここによると、エラーデータについては無視すべきという、一見暴論のような考えが述べられている。エラー処理を厳密にするのはデータの信頼性確保と同義と考えるとトランザクション処理も同様に考えることができると思うので混同して説明する。(私の頭の中ではエラー処理とトランザクション処理を混同していいことになっている)
抜粋すると、

いかに注意深くエラー処理を設計したとしても,広大かつ多様なデータを扱ううちには,誰も考え得なかったような驚くべきデータに出くわすことがある。


それはそうだ。そして、

言わば「狂ったデータ」に対して逐一丁寧な対処を行っていくのは,果たして賢い方法であると言えるだろうか? Pike らは, Sawzall が扱うような種類のデータに限って言えば,そのような「狂ったデータ」を無視してやるだけでシステムの安定性を確保することができると主張する。


これはデータの信頼性の考え方とは全く逆の発想のように思える。言い方をかえると、スケーラビリティを確保するには信頼性を犠牲にしてもしかたがないといったような乱暴な話に聞こえる。トランザクション処理にしても、下手したらパフォーマンスに大きく影響してしまうので、無ければそれに越したことはないが、それを犠牲にするなんて聞いたことがない。

ミッションクリティカルな基幹システムにおいては厳重にチェックされた矛盾のないトランザクションだけがデータベースに格納される。そうしないと様々な部分で弊害が出てシステムが動かなくなる。「狂ったデータ」はデータベースに存在してはならず、入り口のところで逐一丁寧な対処を行っていく必要がある。(基幹システムとは企業の中核を担うビジネスの基本部分をシステム化したものである。システムで処理できなくなると業務が止まるのでミッションクリティカルなシステムとも言われる。)

一方は入力を厳しくして正しいデータだけを管理する方法、他方は狂ったデータも管理するが無視する方法。要は、どうすればスケーラビリティのあるシステムを構築できるか。これがポイントになる。例えば、ミッションクリティカルなシステムであっても、数百件/日、多くて千件/日の受注件数しかなく、蓄積されるデータ量が数G未満であれば、メインフレームを使わなくても構築/運用できる。Java、Tomcatでもいいし、PHPやPerl、Rubyでも、MySQLと組み合わせればそこそこ動くシステムを作ることは可能である。そこそこ動くとは、トラブルがあっても運用でなんとかカバーできるという意味である。(そんなもの、ミッションクリティカルと呼べるか!というIBMの偉い方からツッコミが入りそうだが、クリティカルな業務をなんとか運用できるわけだからOKだ)しかし、千件を超える受注件数であったり、データ量が多かったりすると難易度は非常に高くなる。実際に運用を経験すると分かるが、システムの小さな不安定要素が運用に大きな負荷となって圧し掛かることになる。

ECの受注業務は、単純な定型業務と思われがちだがそんなことはない。バッチがコケたり、通信エラーが発生したり、毎日のように新しいトラブルが発生している。混乱の原因は大体は見当がつく。そう、「狂ったデータ」である。トラブルが発生したとしても、時間内に受注確定作業を完了させて取引先に送信しないと、お客様の希望日に商品が届かなくなってしまうので、逐一丁寧な対処を行い、臨機応変に対応しなければならない。これを解決できないと「狂ったデータ」が蓄積されて、翌日の業務にも支障が出る。さらには「解決数<蓄積数」となった時点で、問題が発散してシステムはダウンする。最悪の場合はサイトクローズだ。安定して動いているように見えるシステムでも、マニュアルによる「待ったなし」の対応をどれだけ現場がやっているか、これが運用の実態なのだ。このように、なかなかシステムは安定しない。安定させるには地味な努力と時間が必要である。なので、否定の16は確かに参考にすべきではない。

16.入力は寛大に受け付け、出力は厳しくせよ(参考にすべきでないより)

ところが、「狂ったデータ」を無視せよという話は、Sawzall が扱うような種類のデータに限ってとはいうものの、システムの安定性を確保することができるという。
「狂ったデータ」を無視せよとは、入力を寛大に受け付けよという意味にもなる。これは限定的とはいえ、非常に興味のある話である。特に興味があるのは、Googleのクローン技術と言われるHadoopやhbaseだ。この記事も参考になる。

私がスケーラブルなシステムに興味があるのは今の実装技術に限界を感じているからだ。私が担当しているECシステムは作ってまだ半年しか経たないのに多量のデータが原因でパフォーマンス劣化を引き起こしているし、これから担当するシステムも同様な問題を抱えて困っている。なんとかならないものか。ムーアの法則がサチッてきて今後もハードウェアによる解決が望めない。そう考えると並列技術しかないが、googleを見るとそれを解決しているように思える。そこに、ミッションクリティカルなシステムをスケーラブルにするヒントがあるかもしれない。そういう意味で、Reflexが参照するデータソースはHadoopでキマリ。Reflex+Hadoopにより、大規模なミッションクリティカルサービスを構築できる可能性が出てきたのは嬉しい限りだ。

よく考えてみれば、この分野はIBMの独壇場だった。大規模なミッションクリティカルシステムを構築できるのはIBMだけ、というのが殺し文句だったはず。いま、この分野で尖がったテクノロジーが見当たらないのは真に残念である。ぜひ今後もこの分野の第一人者であってほしいものだ。(MapReduceはGoogle論文にあるらしいけど、IBM論文ネタとしても最高だよなあ。・誰か書いてる人いるのかなあ)

まあ間違いなく言えるのは、トランザクション処理は厳密にしない、とか、「狂ったデータ」を無視せよとか、一昔前であれば稚拙といわれただろう、ということ。

日曜日, 5月 11, 2008

【EC開発体験記-トランザクション処理-】古くて新しい永遠のテーマ このエントリーを含むはてなブックマーク


トランザクション処理といえば、システムの基本中の基本で古くて新しいテーマである。ミッションクリティカルな基幹システムでは特に重要な要素だと言われて久しい。何で重要なのかはさておき、IBMの新人研修でもそう教えられるので、それが「常識」であることは間違いない。では、トランザクション処理って何のためにあるのか、結論を一言でいうと正しい情報を管理するためである。コミットで正しい情報、ロールバックで正しくないか、まだ受け付けられていない情報となる。長年IBMにいたせいか、ミッションクリティカルな基幹システムはメインフレームが最適という言葉が身に染み付いていて離れない(=洗脳されている)。最初はIBMがいうところの「真のミッションクリティカルな基幹システム」を研究したかった。実際に92年から94年までの担当はメインフレーム一色で、OS390、CICSやIMS、他社メインフレームまでいじっていた。(そういう私が今はLAMPで基幹システムをやるなんて・・思えばかなり冒険したものだと思う。)その時代はホストでもオープン化という大きな波があり、OS390がUNIX APIを搭載しはじめた頃で、新人ではあるものの、元々オープン系を得意としていた私にもいろいろと仕事はあった。MVSでHTTPサーバってどうやって動かすの?あのOSの構造を知っているものには想像すらつかない。実際、他社メインフレームのDBとOS390IHS(HTTPサーバ)を接続したときは自分でも感動したものだ。(この経験がY社での荷物追跡システムに生かされることになるのだが・・)
トランザクション処理はこの頃勉強した。commit/rollbackするまでの間においてACIDを保障する仕組みぐらいしか理解してなかったが、実際、DBやMQなどのサブシステムにはそう説明されているので、仕組みとしてはそれ以上でも以下でもない。しかし本当はもっと奥が深い。例えば、ロックを前提とするものが多いのでパフォーマンスに大きく影響する。また、アプリケーションが複雑になるとトランザクション開始点を覚えておかないといけないので管理が大変になる。実際に運用するとわかるのだが、厳密に管理できてないアプリはすぐにlockwaitで糞づまる。そのlockwaitの原因を紐解くと、トランザクション処理のバグであることが多い。しかし、すぐにはバグを認めたがらない開発者もいるので要注意だ。lockwaitというエラーメッセージの意味だけを説明して「DBサーバの増強が必要です」なんてヌケヌケといってくることもある。(私は直感的にバグと分かるのでカッときてバトルになることが多い)とはいえ、その「lockwaitの原因を紐解く」作業は結構大変だったりする。なぜかというと、実際にエラーメッセージを出している(lockwaitを引き起こしている)処理は無実であり、それより前にロックしている処理の方が有罪であることが多い。しかし、これが簡単には見つからないのである。また、これは私たちのアプリ特有のエラーではあるが、Duplicateエラーも同様に発見が困難である。これは履歴テーブルに受注レコードを移動させる際にキー重複が原因で起こる。移動処理では、受注テーブルにあるレコードを履歴テーブルにINSERTして元の受注テーブルからDELETEする。これが一つのトランザクション処理となっていれば問題ないのだが、エラーとなったトランザクションが正しくrollbackされていないために、残骸となって履歴テーブルに残ってしまっている。次に正しいトランザクションにより移動処理が実行されようとしても、この残骸のためにDuplicateエラーとなってしまっているのだ。結局、これらを解決するために、ソースを全部引っ張り出して「抜け」を探すことになる。「抜け」とは、開始点だけあるような処理、例えば、SELECT FOR UPDATEだけ実行されていてcommmit/rollbackされてない箇所のこと。他人のソースをひっくり返してロジックを追う作業は本当につらい。不具合を見つけたら見つけたで、よく説明しないと開発者との関係がギクシャクする。彼らはバグではないことを主張していたので自分が理解できるまで納得がいかない。そこでトランザクション処理の基本を講義したり、なぜlockwaitになるかをサンプルプログラムでシミュレーションするハメになる。パフォーマンス劣化の原因まで説明するのは本当に難しい。・・・ああ、面倒くさい、もう理解しなくていい。全部AutoCommitでいいよ。となって前記事に載せた、否定の1項目となる。

「5.トランザクション処理は厳密にしない」

これはこれで問題なのは明白だ。いいたいことは要するに、開発者にトランザクション処理を意識させない、にもかかわらず、厳密にトランザクション処理がなされること。AutoCommitのノリで全部書けちゃうのが理想だ。

思いかえすと、メインフレームやDB、Java(JDBC)で使われてきた、commit/rollbackに代表されるコード埋め込み型のトランザクション管理は元々問題あるとされてきた。それだから、EJBのトランザクション(コンテナによるコンテキスト化)に触れたときはショックだった。EJBは結局、コンテナが重くて使い物にならなかったが、Seasarはいけると思った。トランザクションコンテキストをAOPで持ちまわるなんて、なかなか素晴らしいアイデアである。これをJ2EE仕様を損なわずに実現している。しかしまだ不十分な感は否めない。

繰り返しになるが、開発者にトランザクションを意識させている点は大きな課題だ。それがJ2EE仕様を外れたとしても、意識させないことの方が重要だと思う。

一つの解として、すべてをサービス化して、その入り口と出口にトランザクションポイントを置く方法がある。トランザクションスクリプトというデザインパターンであるが、要は、サービスの実行が全部成功すれば、commitとし失敗すればrollbackとする。サービスの最小単位がAtomicとなるのは異論はあるかもしれないが、現時点では、これが一番分かりやすいパターンのような気がする。というわけで、Reflexはトランザクションスクリプトパターンを採用する。サービスの実装はトランザクションを意識せず自由に書いてもらって結構。ただし、エラー時はExceptionを親にスローすること。これだけですべて解決!・・・とうまくいくのかなあ。

 
© 2006-2015 Virtual Technology
当サイトではGoogle Analyticsを使ってウェブサイトのトラフィック情報を収集しています。詳しくは、プライバシーポリシーを参照してください。