doc.dev1x.org

Functional Validationパターン

1. 目的

バリデーションロジックをいい感じに管理したい

2. 課題

ひとつの関数に複数のバリデーションロジックを記述したくない

def example(value):

    result = []

    if len(value.get('name', '')) == 0:
        result.append({
            "message": "名前を入力してください"
        })

    if len(value.get('name', '')) > 127:
        result.append({
            "message": "名前は127文字以内で入力してください"
        })

    if len(value.get('age', 0)) <= 17:
        result.append({
            "message": "18歳未満はご利用頂くことができません"
        })

    # 以下、個別項目の入力チェックが延々と続く
    ...

    return result

3. 解決策

バリデーションロジックは関数に分割し、ループで実行する

value = {
    'name': 'Jhon Doe',
    'age': 17
}
def check_001(value):
    if len(value.get('name', '')) == 0:
        return {
            "message": "名前を入力してください"
        }

def check_002(value):
    if len(value.get('name', '')) > 127:
        return {
            "message": "名前は127文字以内で入力してください"
        }

def check_003(value):
    if value.get('age', 0) <= 17:
        return {
            "message": "18歳未満はご利用頂くことができません"
        }
def validate(value, *functions):
    result = []
    for f in functions:
        validate_result = f(value)
        if not validate_result is None:
            result.append(validate_result)
    return result
value = {
    "name": "Jhon Doe",
    "age": 17
}
result = validate(
    value,
    check_001,
    check_002,
    check_003,
)
print(result)   # <= [{'message': '18歳未満はご利用頂くことができません'}]

4. メリット

バリデーションロジックを個別に管理できる

バリデーションロジックを自由に組み合わせることができる

5. デメリット

関数が増える

6. 注意

目的に見合ったバリデーションライブラリが存在するならそちらを使う

移植性/再利用性はそこまで高くはない

参考資料

Appendix-1 ソースコード全体

def validate(value, *functions):
    result = []
    for f in functions:
        validate_result = f(value)
        if not validate_result is None:
            result.append(validate_result)
    return result

def check_001(value):
    if len(value.get('name', '')) == 0:
        return {
            "message": "名前を入力してください"
        }

def check_002(value):
    if len(value.get('name', '')) > 127:
        return {
            "message": "名前は127文字以内で入力してください"
        }

def check_003(value):
    if value.get('age', 0) <= 17:
        return {
            "message": "18歳未満はご利用頂くことができません"
        }


if __name__=="__main__":
    value = {
        "name": "Jhon Doe",
        "age": 17
    }
    result = validate(
        value,
        check_001,
        check_002,
        check_003,
    )
    print(result)   # <= [{'message': '18歳未満はご利用頂くことができません'}]