型付データセットを使うことで複雑なSQLはコードの外に追い出す

私のシステム設計の指針は複雑度の分散です。

だからコードの中に複雑なクエリは書きません。

問い合わせのクエリは、何らかの目的があって発行するものなので、JOIN等複数のテーブルが絡む複雑なクエリは、すべてRDBMS側にViewとして持つようにしています。

追記:2009年あたりから、ストアドも対象とするようにしました。

そのためのツールとして型付データセットを利用しています。

そうすることで、コードの中では常に一つのオブジェクト(テーブルであれVIEWであれ)に対して操作をするだけで済むので複雑なクエリが発行される事はありません。

それを実現するために、SQLを直接書かないでクエリを発行するためのフレームワークを作成してあります。

詳しくは書きませんが、型付データセット内で定義されているテーブル単位にラップしているクラスがあり、それに条件を積み上げたオブジェクトをアタッチする事で、様々なクエリを発行するようになっています。

ラップしているクラス自体の定義は前もって用意してあるテーブルにアクセスするための機能を持った抽象クラスを継承してテーブルを指定しているだけなので、数行で済むため作成やメンテナンスの労力はほぼありません。

SQLServerやOracleといった製品毎の差異もフレームワーク側で吸収(.NET Framework2.0にはその機能が実装されていて、そっちに移行しようか悩みました。)し、その中でクエリを自動生成するようにしているため、システムをコーディングするときは、仕様にあうように条件オブジェクトをブロックのように積み上げて、ラップしているクラスにアタッチするだけで良いです。

また、内部でパラメタライズドクエリ(+必要なエスケープ)を作成するためSQLInjectionといった問題も発生しません。

また、ラップするクラス名はテーブルやビューと同じ名前にしてある事と、ビューは目的に応じた名称にしている事から、何が目的でデータにアクセスしているのかが一目瞭然になっています。

この取り組みはもう4年以上続けていますが、

  • 複雑なクエリは外(RDBMS側)に目的に応じたVIEWとして持っている
  • クエリがコードの中に点在しない
  • コード内でデータにアクセスしている部分へのアクセスやコントロールがしやすい

などから昔のシステムでも保守にかける時間が少なくて済んでいます。

また、開発時にコードが複雑になりすぎないため、それ以前に比較すると不具合が少なかったり、不具合が発生してもそれにかける作業時間が少なく済んでいます。

コードの中に複雑なクエリを書いてしまって、修正が大変だとか嘆いている、そこのあなた。

コードにとっての保守フェーズはコードは書いた瞬間から始まっています。

とりあえず複雑なクエリはRDBMSに持ってもらう事でコードの見通しを良くしてみてはいかがでしょうか。

Share

チームマイナス6%

エコバック使おうとか、

比較的インパクトが少ない事は言えても、

電車で行けるところの国内線廃止とか、

電車で行けるところは飛行機を使うのを控えようとか、

各種国際会議等はビデオチャットを利用した分散会議とか(テロ対策にもなると思う。)、

分散オフィス促進とか、

海外旅行は控えて国内旅行にしようとか、

これから建築する建物は原則、外断熱にしろとか(一部の建物は最近、そういう原則が作られたけど)、

せめて無断熱な新築は犯罪にするとか、

アルミサッシは廃止で樹脂サッシにしようとか、

Q値(熱損失係数)という言葉を、もっと一般的にしようとか、

自分の家のQ値を知ろうとか、

エアコンの性能は固定の適用する畳数表示をやめて、容量の表示にしようとか、(Q値によっては、14畳用一つで全部屋まかなえる。うちはそう。)

という事は言えないんだよね。

だからって、どうという事は無いんだけど。

ところで排出権取引ってCO2削減に一切貢献していないのでは?

よく分からないで言っているけど。

カーボンオフセットTシャツっていうのには、笑った。

「このTシャツを買うと、一定量の排出権が買えるので差し引きCO2排出量が減ります。」

という趣旨なんだけど、ただの数字合わせだし、カーボンオフセットTシャツ自体の、製作や販売に係る部分については明らかにカーボンオフセットじゃ無いんだろうし。

まぁ、私個人の意見としては、CO2排出量削減に関しては特に言うことも思うこともないんだけれど、これまでの時代の流れをみても、ちょっとずつテクノロジが解決していく事だと思っています。

でも、この取り組みを特に批判しているわけでも否定しているわけでもなく、そう思ったというだけの話。

やらないよりは、やった方がましだし、継続する事で、いくつかの問題点が解消され取り組み自体が発展していくだろう。

というか、今までも発展してきたんだね。

ただ、ちょっと今の取り組み方(クールビズという名の我慢系とか)は続かないダイエットみたいだなと思った。

Share

まだ、SQLServer2005が最新版

現時点において、出荷されている製品としてはまだSQLServer2005が最新版である。

SQLServer2008はまだ未出荷だから最新版ではない。

当たり前の話だけど、勘違いしている人もいるようなので、とりあえずメモ。

Share

ATOK 2007 for Macを買った

IME2007よりはましだと思って、ことえりをだましだまし使っていたのだが、やっぱりATOKが恋しくなったので、ダウンロード購入をした。

WindowsではATOKユーザなので、まだ、インストールもしていないけど、使い勝手に期待。

Share
カテゴリー: Tool

どうも日本語コーディングが出来ない

日本語の変数名とかメソッド名とかクラス名といった識別子が使えないっぽい。

日本語でかけると良いんだけど。

できるという記事をどっかで見たような気もするんだけどなぁ。

まぁ、

識別子 (または 名前 (name)) は、以下の字句定義で記述されます:

identifier ::= (letter|”_”) (letter | digit | “_”)*

letter ::= lowercase | uppercase

lowercase ::= “a”…”z”

uppercase ::= “A”…”Z”

digit ::= “0”…”9″

http://www.pinkdragon.net/doc_lib/contents/ja/python_man/ref/identifiers.html

を見る限りは出来ないで正解だとは思うんだけど。

Share

それにしても口唇ヘルペスに初めてなりました。

唇の下がいたいやら痒いやら、リンパが腫れてちょっと痛いし、同居人に感染させないように気を使う必要があるやらで疲れ気味。

疲れ気味だから感染するんだけども。

まぁ、薬を貰ったので早く治ったら良いなぁ。

最近の生活リズム崩しまくりで変な時間にしか眠れないのもあったので、そこを改善する必要あり。

Share

フォーム入力した値の検証について気づいた事

ずっと、GETのリクエストハンドラとPOSTのリクエストハンドラで違うルーティングをするものだと思っていたので、POSTで検証が通らなかったときにどうやって、その情報を渡しつつ元のフォームに戻るんだろうか。

という疑問があったが、form validation frameworkのサンプルページを読んでいたら、どうもGETもPOSTも同じリクエストハンドラで処理するみたいだ。

だったら、話は簡単だと思うわけで、特にform validation frameworkを利用しなくても良くて、ただ

class ****#リクエストハンドラ
def get(self):
  初期表示
def post(self):
#入力値検証
#検証が通らなかったらgetのレンダリング+エラーメッセージ
#通れば、post処理をした後、getのレンダリング

といった感じでも良さそう。でも、後学の為にもframeworkを使ってみても良さそう。

そういえば、ASP.NET MVC Previe2でも同じサンプルアプリケーションを作成する事で、構成の違いが載せられたら良いなぁ。

まだ良いなぁ。でしかないが。

間に合えば、CLTで使えたら、面白いかも?

Google App EngineとVisual Studio2008+MVC Preview2を使ったネタは許されないかも?

Share

私のブログのエントリにおけるスタンスを自分なりに考えてみた

基本的に、思想カテゴリやコーディングカテゴリでは、どのプラットフォームにも適用できるような抽象的な事ばかりを狙って書いてきたつもり。

それゆえに即テクノロジとつながっているわけではないので、テクノロジに興味のある層へのリーチは薄いかも。

あとは、抽象的な事って気づいてしまえば結構単純なので、ネタ切れになりやすい。(特に貯蓄しているわけではないが。)

まぁ、最近は比較的拘らないで、C#やVBの新しい構文やgoogle app engineなどの.NET以外の話題も書いているんだけど(つっても、ここ1週間だけど)。

できるだけ、基本的な部分にとどめて、結局はほかの言語に読み替えても使えるぜ的な、つまり抽象的に解釈できるような記事が多いと思っている。

まぁ、でも正直なところ「何にも拘らない」「良いものは良い。」というのが私のスタンスなので、そんな感じで気づいた事をボンボン書いていこうかと。

そういえば、最近、「ナベアツ問題」で検索してもヒットしなくなりました。

零細ブログだけに。

まぁ、でも、どうでもいい事。

コードの10戒は、初稿どころか草案くらいのものなので、ちゃんと改訂して仕事に使いたい。

最近、がちがちのコーディング標準に関しては、フレームワーク+コードの10戒レベルのルールでリプレースできるのではないかと思っています。

この件については、また別の機会にエントリできたら良いなぁ。

Share

formに入力した値の検証の仕方について

MVCの仕組みになれていなかったのでformに入力した値の検証の仕方が分からなかったが、DJangoのform validation frameworkという機能を利用するらしい。

後で使うので、説明しているページをメモ。

Introduction

In this article we show how to use Django’s form validation framework with the Google App Engine. This framework allows you to construct HTML forms from your data models, and handle the inputted information from the forms seamlessly when interacting with the datastore.

The Django Form Validation Framework on Google App Engine

Share

DJangoのテンプレートを利用したサンプルアプリの作成

最初に、サンプルアプリの仕様を説明

概要:
・1からインクリメントしながら番号を表示し、3の倍数と3の付く数字のときに別の文字列を、5の倍数のときにも更に別の文字列を表示する。
・終点となる番号が設定できる。
・3の倍数と3のつく数字のときに読み上げる文字列を設定できる。
・5の倍数のときに読み上げる文字列を設定できる。
・設定した値はユーザ毎に永続化される。
・アプリケーション名は世界の○○とする。○○のところはユーザのnicknameを設定したい。
・formのvalidationについては、今回は行わない。(本当は行う必要があるので、次回以降のエントリで機能追加します。

最終的に作成したい画面イメージ

f:id:NAL-6295:20080516130415j:image

それではユーザ毎の設定値を保持するためのモデルを定義したいと思う。

ユーザ毎の設定値なのでとりあえずクラス名は、nabeatsuUserMasterとし、db.Modelを継承する。

継承することで、Google App EngineのDataStoreを利用して永続化するためのモデルと認識される。

あとは、サンプルのコメントで説明する。

#の行がコメント。

青色になっていると思う。

#DataStoreの機能を利用するためにimportする。
from google.appengine.ext import db
#db.Modelを継承することで、永続化するためのモデルとして認識される。
#RDBのテーブルのようなものです。
#項目は項目名 = db.型Property(パラメータ)で定義される。
class nabeatsuUserMaster(db.Model):
#この設定のユーザを識別するための項目でUser型の項目にします。
 #GoogleにログインしているUser情報が保存される。
author = db.UserProperty()
#終点となる番号をInteger型の項目とします。
count = db.IntegerProperty()
#とりあえず、利用しませんが、このデータの更新日時をDateTime型の項目とします。
date = db.DateTimeProperty(auto_now_add=True)
 #3の倍数と3のつく数字の時に読み上げたい文字列をString型の項目とします。
saidWhen3 = db.StringProperty()
 #5の倍数の時に読み上げたい文字列をString型の項目とします。
saidWhen5 = db.StringProperty()

これで、とりあえず、nabeatsuUserMasterは完成です。

つづいて設定と読み上げを行うためのページを定義します。

ここで、DJangoのテンプレートを作成します。ファイル名は、なんでも良いのですが、とりあえずindex.htmlとします。

基本的な機能だけを利用していますので先に利用している機能を説明します。

{{ parameterName }}

で後で説明するwebのリクエストをハンドラで定義したパラメータを出力します。

{% 構文 %}

とすることである程度の構文が使えます。今回はfor value in valuesのみ利用していますので、

valuesからvalueを取り出して出力という処理を

{% for value in values %}

{{ value }}

{% endfor %}

といった感じで実装しています。

後で説明するリクエストハンドラから受け付けているパラメータ名の説明をしておきます。

userName=今ログインしているユーザのnickname

logoutUrl=ログアウトするためのUrlです。

count=読み上げる数字の終点です。

saidWhen3=3の倍数と3のつく数字のときに表示する文字列

saidWhen5=5の倍数のときに表示する文字列

outputValues=読み上げた文字列のリストです。

index.htmlの内容です。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html lang="ja" xml:lang="ja" xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>世界の{{ userName }}</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link href="/nabeatsu/styles/styles.css" type="text/css" rel="stylesheet" />
</head>
<body>
<div id="header">
世界の{{ userName }} <a href="{{ logoutUrl }}">ログオフ</a>
</div>
<form action="/nabeatsu/set" method="post">
<div>読み上げてほしい数:<input type="text" name="count" value="{{ count }}" tabindex="1" accesskey="c" /></div>
<div>3の倍数と3のつく数字のとき<input type="text" name="saidWhen3" value="{{ saidWhen3 }}" tabindex="2" accesskey="3" />になって、</div>
<div>5の倍数のとき<input type="text" name="saidWhen5" value="{{ saidWhen5 }}" tabindex="3" accesskey="5" />になります。</div>
<div><input type="submit" value="設定"  tabindex="4" accesskey="s" /></div>
</form>
<div id="body" >
{% for value in outputValues  %}
{{ value }} <br />
{% endfor %}
</div>
</body>
</html>

つづいて設定を更新するためのリクエストハンドラを作成します。

ここで、前もって作成しておいたnabeatsuUserMasterにデータを追加・更新する部分の作成します。

前段のテンプレートどおり、/nabeatsu/setに対応したハンドラとして、webapp.RequestHandlerを継承してUserMasterUpdateというクラスを作成します。

今回は、nabeatsuUserMasterと1対1で対応していますので、同じスクリプトファイルに記述されている前提です。

例によってサンプル中のコメントで説明します。

#dataStoreを利用するためのインポート
from google.appengine.ext import db
#web applicationを作成するための機能をりようするためのインポート
from google.appengine.ext import webapp
#googleのユーザ機能を利用するためのインポート
from google.appengine.api import users
#nabeatsuUserMasterクラスは前のセクションで説明したものですので、読み飛ばしてください。
class nabeatsuUserMaster(db.Model):
author = db.UserProperty()
count = db.IntegerProperty()
date = db.DateTimeProperty(auto_now_add=True)
saidWhen3 = db.StringProperty()
saidWhen5 = db.StringProperty()
#webapp.RequestHandlerを継承して作成することで、リクエストハンドラとしての機能を持ちます。
class UserMasterUpdate(webapp.RequestHandler):
#今回は、データの設定なのでpostメソッドを作成します。HTTP POSTに対応しています。
def post(self):
#現在ログオン中のユーザを取得します。
user = users.get_current_user()
#ユーザが存在しない場合、users.create_login_urlメソッドを利用して、ログインページにリダイレクトします。
if not user:
self.redirect(users.create_login_url(self.request.uri))
return
#index.htmlのテキストボックスcountの値が入力されていなかったら、何もしないで元のページにリダイレクトします。
#今回は、form validationを行わないので、この程度にしておきます。
if self.request.get('count').count == 0:
self.redirect('/nabeatsu/')
return
#nabeatsuUserMasterに対象ユーザのユーザ情報を検索条件として、検索します。
#GQLというクエリに良く似たものを利用しています。
Counts = nabeatsuUserMaster.gql("where author = :author",author=user)
#取得したインスタンスリストの件数が0件の場合は、新しいnabeatsuUserMasterインスタンスを作成します。
#そうで無い場合は、取得したインスタンスリストの先頭を取得します。
if Counts.count() <= 0:
Count = nabeatsuUserMaster()
else:
Count = Counts[0]
#各種フォームデータをインスタンスに設定します。
Count.author = user
Count.count = int(self.request.get('count'))
Count.saidWhen3 = self.request.get('saidWhen3')
Count.saidWhen5 = self.request.get('saidWhen5')
#putでdataStoreに永続化されます。
Count.put()
#設定が終わったところで最初のページにリダイレクトして終了です。
self.redirect('/nabeatsu/')

つづいて、一番肝心なindex.htmlに対応したリクエストハンドラを作成します。

ここで、テンプレートであるindex.htmlに対応したリクエストハンドラを作成します。

このリクエストハンドラ内で、該当ユーザ毎のデータを持ってきて、読み上げた数字のリストを他のデータとともに、テンプレートに渡しています。

例によってサンプル内のコメントで説明します。

# -*- coding: utf-8 -*-
import wsgiref.handlers
import cgi
import os
from google.appengine.ext import db
from google.appengine.ext import webapp
from google.appengine.api import users
#テンプレート機能をつかうために意ポート
from google.appengine.ext.webapp import template
#先ほど定義した、nabeatuUserMasterとそれを更新するためのリクエストハンドラをインポート
import masters
#MainPageという名前でwebapp.RequestHandlerを継承して実装します。
class MainPage(webapp.RequestHandler):
#数字を読み上げるロジックを実装しています。
def nabeatsu(self,saidWhen3,saidWhen5):
def reader():
isAho = reader.value % 3 == 0 or str(reader.value).find('3') >= 0
isDog = reader.value % 5 == 0
outputValue = ''
if isAho:
#引数で渡されたsaidWhen3をUTF-8にエンコードした上で、有害な文字列が実行されないようにエスケープ処理
outputValue += cgi.escape(saidWhen3.encode('UTF-8'))
if isDog:
#引数で渡されたsaidWhen5をUTF-8にエンコードした上で、有害な文字列が実行されないようにエスケープ処理
outputValue += cgi.escape(saidWhen5.encode('UTF-8'))
if not isAho and not isDog:
outputValue = cgi.escape(str(reader.value))
reader.value += 1
return outputValue
reader.value= 1
return reader
#HTTP GETに対応した、メソッドを実装します。
def get(self):
#現在のユーザ情報を取得します。
user = users.get_current_user()
#ログインしていなければ、ログインページにリダイレクトして終了します。
if not user:
self.redirect(users.create_login_url(self.request.uri))
return
#各種初期値を設定しておきます。
#読み上げる数字の初期値は40で、3の倍数と3のつく数字のときは'あほ!'になって、5の倍数のときは'わん!'となるのがデフォルトです。
count = 40
saidWhen3 = u'あほ!'
saidWhen5 = u'わん!'
#自分自身の設定データをクエリで取得します。
currentCount = masters.nabeatsuUserMaster.gql("where author = :author",author=user)
#データが存在してれば、各種データを設定します。
if currentCount.count() > 0:
count = currentCount[0].count
saidWhen3 = currentCount[0].saidWhen3
saidWhen5 = currentCount[0].saidWhen5
#nabeatsuメソッドに引数を渡して、readerメソッドのインスタンスを取得します。
reader = self.nabeatsu(saidWhen3,saidWhen5)
#出力データリストを初期化します。
outputValues = []
#設定された数字まで、reader()を実行し続けます。
for i in xrange(1,count + 1,1):
#戻り値が出力する文字列になっていますので、outputValuesに対してappendで追加していきます。
outputValues.append(reader())
#ここでテンプレートに渡すパラメータ群を設定します。
#logoutUrlをcgi.escapeしているのは、url中に&が出現しているから。
template_values = {
'userName' : user.nickname(),
'logoutUrl': cgi.escape(users.create_logout_url("/nabeatsu/")),
'count' : count,
'saidWhen3' : saidWhen3,
'saidWhen5' : saidWhen5,
'outputValues': outputValues,
}
#このスクリプトとテンプレートが同じフォルダにおいてあるので、テンプレートの完全なパスを作成します。
path = os.path.join(os.path.dirname(__file__), 'index.html')
#template.renderにテンプレートとテンプレートに渡すパラメータ群を設定することで、実際のhtmlが出力されます。
self.response.out.write(template.render(path, template_values))
#最初に実行されるmainメソッドです。
def main():
#ここで、パスとリクエストハンドラを紐付けています。debug=trueの間はエラーのときに、tracebackが表示されます。
application = webapp.WSGIApplication(
[('/nabeatsu/', MainPage),
('/nabeatsu/set', masters.UserMasterUpdate)],
debug=True)
wsgiref.handlers.CGIHandler().run(application)
if __name__ == '__main__':
main()

最後にGoogle App Engineのアプリケーション定義をして終了です

上から、

アプリケーションの名前

バージョン

ランタイム(pythonしかないので固定)

apiのバージョン(いまのところ1)

handlersはurlとスクリプトの割り当て

/nabeatsu/とnabeatsu.pyを割り当て

/nabeatsu/setとnabeatsu.pyを割り当て

スタイルシートのパスを割り当て

application: nabeatsu
version: 1
runtime: python
api_version: 1
handlers:
- url: /nabeatsu/
script: nabeatsu.py
- url: /nabeatsu/set
script: nabeatsu.py
- url: /nabeatsu/styles/
static_dir: styles

以上で、完成です。

スタイルシートについては適当に設定してあげれば良いですが、一応サンプルとして添付しておきます。

#body
{
color:blue;
font-weight:bold;
height:200px;
overflow-y:scroll;
border:solid 1px black;
}
body
{
color:Black;
margin: auto;
FONT-FAMILY: "MS ゴシック";
FONT-SIZE: 9pt;
FONT-WEIGHT: normal;
LETTER-SPACING: normal;
TEXT-TRANSFORM: none;
background-color: #FFFFFF;
WORD-SPACING: normal;
}

とりあえず、簡単なサンプルですが、用意した機能の基本的な部分を利用しながら作成する手順については追えるのではないでしょうか。

Share