Шаблон для непроверенных данных, проверенных данных и проверки - PullRequest
0 голосов
/ 05 ноября 2019

В моем Ktor REST API я получаю JSON. Для меня это удобно десериализовать в простые классы данных kotlinx.serialization. Прежде чем я смогу продолжить использовать эти данные, я должен применить некоторую проверку. Затем я могу передать его другим частям кода.

Я хотел бы получить четкое разделение между десериализованными, но не проверенными данными, самой проверкой и проверенным кодом.

Например, у меня есть кое-что с эффектом:

@Serializable
data class Input(val something: Int)

fun Input.validate() {
    if(something < 5) { throw WhateverException("invalid") }
}

Но есть несколько проблем с этим: При получении экземпляра Input в любом месте, я не могу быть уверенэто уже было подтверждено, поэтому, чтобы быть в безопасности, я позвоню validate() снова.

Итак, чтобы избежать этого, я бы хотел, чтобы validate() возвратил некоторую версию Input, которая сообщает мне, что данные действительны, и чтобы я мог иметь сигнатуры методов в моей кодовой базе, которые принимают только проверенные данные.

Я знаю, что могу скопировать класс Input в частный класс с именем ValidatedInput и получить validate(), который возвращает это, но это похоже на дублирование кода. (В итоге я получу десятки классов, таких как Input.) Также это будет препятствовать тому, чтобы интерфейс, указывающий Input, имел метод validate и возвращал что-то вроде Validated<Input>

Как мне спроектировать свои классы и методы, чтобы четко выразить это разделение, без повторения кода?

Ответы [ 2 ]

1 голос
/ 05 ноября 2019

Если вы хотите убедиться, что Input проверен, вы должны как-то поместить его в систему типов. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Validated<Input> для вас не разрешается (я бы предпочел сделать это таким образом), возможно, вы могли бы изменить его, предоставив Input какой-то токен проверки в качестве параметра типа?

Например,вот так:

data class Input<out Validation>(val something: Int)

sealed class Validation
object Validated : Validation()
object NotValidated : Validation()

fun Input<NotValidated>.validate(): Input<Validated> {
    return if(something < 5) { throw RuntimeException("invalid") }
    else Input<Validated>(something)
}

Делать это иначе (Validated<Input>), на мой взгляд, немного более гибко. Он также не смешивает код проверки в класс Input (как это сделал бы токен проверки). Так, например, вы можете сделать что-то вроде этого:

sealed class Validated<T>
class Valid<T>(val value: T) : Validated<T>()
class Invalid<T>(val error: Exception): Validated<T>()

fun Input.validate(): Validated<Input> {
    return if(something < 5) Invalid(RuntimeException("invalid"))
    else Valid(this)
}

fun performAction(input: Valid<Input>) { 
    TODO("Do something with the valid Input")
}

fun main() {
    val validated = Input(5).validate()

    val result = when(validated) {
        is Valid -> performAction(validated)
        is Invalid -> throw validated.error
    }

    println(result)
}
0 голосов
/ 05 ноября 2019

Вы можете переместить проверку на Input инициализацию:

@Serializable
data class Input(val something: Int) {
    init {
        validate()
    }
}

Если вы сделаете это, все Input экземпляры будут действительными.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...