データベーストランザクションについて

これなに?

トランザクションのことをよくわかってなかったので調べた結果

transactionとは

不可分な一連の処理、ワンセットの処理単位。 データベースでは、データベースに対して行われる一つ以上の更新処理。 トランザクションによってデータ更新処理の確定や取り消しを管理できる

トランザクション作成方法

     START TRANSACTION;
     処理
     COMMIT;

COMMIT

トランザクションを確定させる処理。一度コミットした結果はもとにもどせない

ROLLBACK

ROLLBACKはトランザクションを取り消す処理。トランザクション開始前の状態に戻る

ACID特性

トランザクションには4つの特性が標準規格によって定められている。

Atomicity(原子性)

トランザクションが終わったときに、そこにふくまれていた更新処理はすべて実行されるか、全てじっこうされない状態でおわること(all or nothing)を保証する性質。 COMMITで更新が確定されるかROLLBACKでもとに戻されるかの二択なイメージ

Consistency(一貫性)

トランザクションに含まれる処理はすべて成約を満たす性質 トランザクションの途中で制約違反の処理が会った場合、処理を中断してトランザクション実行前に戻す。

Isolation(独立性)

あるトランザクションの実行中に、ほかのトランザクションの影響をうけない性質。 トランザクション実行中に他のトランザクションがデータの更新しても実行中のトランザクションはその更新結果の影響をうけない

Durability(永続性)

トランザクションが完了後データが保存され失われない性質。 この性質の保証のために、一般的にトランザクションのログを記録しておき障害がおきた場合はログを使用して障害発生前の状態に復旧する

Django REST frameworkのvalidation

Django REST frameworkのvalidationについて調べたのでまとめ

Serializerについて

serializerについて概要だけ。 Serializerはクエリセットやモデルインスタンスなどの複雑なデータをpythonデータ型に変換してjsonレンダリングできるようにします。 汎用的なSerializerClassとモデルインスタンスとクエリセットを扱う際の便利なショートカットとしてModelSerializerがあります。 DjangoのModel定義がしてある場合は、ModelSerializerを使うといいんじゃないかと思います。が、今回はシンプルにSerializerClassを使います

Serializerを作る

たとえば検索APIで入力を検証したいとします。 単純にキーワードが文字列であるか検証するだけであれば一瞬です。

from rest_framework import serializers


class SearchValidationSerializer(serializers.Serializer):
    keyword = serializers.CharField()

シェルで確認してみます. DRFではバリデートした結果はis_valid()で確認できて、バリデーション済みのデータはvalidated_dataでアクセスできます。ちなみにvalidated_dataはis_validを呼んだあとでアクセスできるようになります。定義していないフィールドはきちんと無視されてますね。

> b = SearchValidationSerializer({'keyword': 'python', , 'aaa': 'iii'})
> b.is_valid()
True

> s.validated_data
OrderedDict([('keyword', 'python')])

validationが失敗した場合 error内容はerrorsでアクセスできます。

> b = SearchValidationSerializer(data={'keyword': None})
> b.is_valid()
False

> s.erros
ReturnDict([('keyword', ['This field may not be null.'])])

カスタムバリデーション

まあ簡単にバリデーションできるよってのはわかったけど、実際は独自のバリデーションが必要なんだけどってなると思います。

カスタムバリデーションを作る方法は、関数ベースとクラスベースの2つの方法があります。

関数ベース

関数ベースの場合、バリデーションの対象にする範囲によって方法が変わります。

フィールドレベル

特定のフィールドに対して独自の検証をしたい場合、サブクラスにvalidation_<field name>というメソッドを追加することで可能です。 例えば、前と同じ検索でも「python」っていうワードを含んでないとゆるさないという場合はこんな感じです。

class SearchValidationSerializer(serializers.Serializer):
    keyword = serializers.CharField()

    def validate_keyword(self, keyword):
        if 'python' not in keyword:
            raise serializers.ValidationError("your keyword has no python")
        return keyword


> s = SearchValidationSerializer(data = {'keyword': 'python'})
> s.is_valid()
True
> s = SearchValidationSerializer(data = {'keyword': 'ruby'})
> s.is_valid()
False

オブジェクトレベル

オブジェクトレベルでバリデーションしたい、例えば複数のフィールドをまたいで検証を行いたい場合などは、サブクラスにvalidateメソッドを追加します。 validateメソッドはフィールドの値をもつdictionaryを一つ引数に持ちます。 python含んでないと検索できないっていうのは厳しすぎるから止めてあげよう、ただしpythonistaお前らはダメだっていう場合はこんな感じ

class SearchValidationSerializer(serializers.Serializer): 
    keyword = serializers.CharField()
    user = serializers.CharField() 
    
    def validate(self, data):
        if 'python' not in data['keyword'] and data['user'] == 'pythonista':
            raise serializers.ValidationError("your keyword has no python")
        return data


> s = SearchValidationSerializer(data = {'keyword': 'ruby', 'user': 'rubist'})
> s.is_valid()
True
> s = SearchValidationSerializer(data = {'keyword': 'ruby', 'user': 'pythonista'})
> s.is_valid()
False

クラスベース

検証ロジックを再利用できるようにするには、こちらを使うほうがいいと思ってます。 クラスベースのvalidatorを作成するには__call__メソッドを使います。

フィールドベースのserializerをクラスベースにすると次のようになります

class KeywordValidator:

    def __call__(self, keyword):
        if 'python' not in keyword:
            raise serializers.ValidationError("your keyword has no python")
        return keyword

クラスベースのカスタムバリデーションは以下のようにsirealizerで指定します。

class SearchValidationSerializer(serializers.Serializer):
    keyword = serializers.CharField(validators=[KeywordValidator()])

s = SearchValidationSerializer(data = {'keyword': 'ruby'})

validatorsには複数のvalidatorを指定できます。

バリデーションの順番

これらvalidatorがどういう順番で実行されるかというと

  1. validate_の実行
  2. validatorsに指定したバリデーションの実行
  3. validateメソッドの実行

の順番になります。

これまでの話とは少し外れますが、ModelSerializerを使っている場合は注意が必要です。

class User(models.Model):
    name = models.CharField()
    uid = models.CharField(unique=True, max_length=20)


class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User

    def validate_uid(self):
        # do something

djangoのmodelでフィールドにuniqueがついている場合、DRFは明示的にvalidatorを追加します。

> s = UserSerializer()
> print(repr(serializer))
・・・省略・・・
uid = CharField(validators=[<UniqueValidator(queryset=User.objects.all())>])

この場合のシリアライザレベル(validators)の処理は、フィールドレベル (validate_)の処理の後になるため、うっかりしているとハマってしまい不毛な時間を過ごします...

まとめ

DRFのsearilzer便利みたいなところはありますが、バリデーションも簡単に実装できます。 ModelSerializerを使った場合等、もう少しバリデーションの話はあるんですが長くなるのでこの辺で。