2017-02-18 5 views
2

Таким образом, наш проектный проект является приложением Java 8 Springboot, springboot позволяет вам сделать что-то очень просто. ех, запрос проверки:Использование монад в приложении Springboot для исключения исключений

class ProjectRequestDto { 
    @NotNull(message = "{NotNull.DotProjectRequest.id}") 
    @NotEmpty(message = "{NotEmpty.DotProjectRequest.id}") 
    private String id; 
} 

Когда это ограничение не соответствует, весна (? springboot) на самом деле вызывает исключение проверки, как таковой, мы поймаем его где-нибудь в приложении и построить (Bad Request) ответ 404 для наше приложение.

Теперь, учитывая этот факт, мы своего рода следовали той же философии на протяжении нашего приложения, то есть, на более глубоком уровне приложения мы могли бы иметь что-то вроде:

class ProjectService throws NotFoundException { 
    DbProject getProject(String id) { 
     DbProject p = ... // some hibernate code 
     if(p == null) { 
      Throw new NotFoundException(); 
     } 

     return p; 
    } 
} 

И снова мы поймаем это исключение на более высокий уровень и построить еще 404 для клиента.

Теперь, это вызывает некоторые проблемы:

  1. Наиболее важным из них: Наша ошибка отслеживания перестает быть полезной, мы не можем дифференцировать (легко), когда исключение имеет важное значение, потому что они происходят все время , поэтому, если служба внезапно начинает бросать ошибки, мы не заметим, пока не станет слишком поздно.

  2. Большое количество бесполезных протоколов, например, при запросах на регистрацию, пользователь может ошибаться в своем пароле, и мы регистрируем это и в качестве второстепенной точки: наша аналитика не может помочь нам определить, что мы на самом деле делаем неправильно, мы видим много из 4xx, но это то, чего мы ожидаем.

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

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

Теперь, к моему фактическому вопросу: когда я спросил одного из наших архитекторов, он предложил использовать монады (как временное решение ofc), поэтому мы не модифицируем нашу архитектуру, а решаем наиболее загрязняющие конечные точки (ex неправильный логин) в краткосрочной перспективе, однако я борюсь с парадигмой монады в целом и даже больше в java, я действительно не знаю, как применить ее к нашему проекту, не могли бы вы мне помочь? некоторые фрагменты кода будут действительно хорошими.

TL: DR: Если вы используете универсальное приложение для загрузки весны, которое вызывает ошибки как часть потока данных, как вы можете применить шаблон монады, чтобы избежать ненужного ввода данных и временно устранить эту ошибку как часть данных архитектуры потока.

+2

Я все еще не понимаю, в чем проблема с исключениями? Они имеют свои собственные типы, поэтому вы можете легко различать их и анализировать их, например, вы можете считать NotFoundException «нормальным», некоторые пользовательские CredentialsDontMatchException (которые будут выбрасываться вашим кодом, а не весной) как «тривиальные», и NullPointerException как добрый критический (поскольку это, вероятно, означает серьезную ошибку). –

+0

Да, это еще один способ сделать это, создать некоторые «приглушенные» исключения, которые мы не регистрируем, одна проблема с этим связана с точкой 3, исключения являются дорогостоящими и делают их частью нашего нормального потока данных, будут ухудшать производительность в конечном счете, мой вопрос идет немного дальше, чтобы попытаться понять, как использовать MONADS, чтобы временно исправить нашу проблему здесь, поэтому, пожалуйста, побалуйте меня :) – ospfranco

+0

Я согласен с Филиппом здесь. Исключения стоят немного больше, чем что-то возвращать, но они делают ваш код намного проще понять и поддерживать. Вы не должны пытаться оптимизировать, если у вас нет проблем с производительностью.И добавить еще один сервер в большинстве случаев дешевле, чем потерять время, поддерживая код, который может быть проще. Я бы просто использовал правильные типы исключений и правильные коды состояния HTTP. Например, отказ аутентификации должен вернуть 401, а не 404 (что означает Not Found, BTW, а не Bad Request). –

ответ

3

Стандартный монодальный подход к обработке исключений заключается в том, чтобы поместить результат в тип, который является либо успешным результатом, либо ошибкой. Он похож на тип Optional, хотя здесь вы имеете значение ошибки вместо пустого значения.

В Java простейшая реализация нечто вроде следующего:

public interface Try<T> { 

    <U> Try<U> flatMap(Function<T, Try<U>> f); 

    class Success<T> implements Try<T> { 
     public final T value; 

     public Success(T value) { 
      this.value = value; 
     } 

     @Override 
     public <U> Try<U> flatMap(Function<T, Try<U>> f) { 
      return f.apply(value); 
     } 
    } 

    class Fail<T> implements Try<T> { 
     // Alternatively use Exception or Throwable instead of String. 
     public final String error; 

     public Fail(String error) { 
      this.error = error; 
     } 

     @Override 
     public <U> Try<U> flatMap(Function<T, Try<U>> f) { 
      return (Try<U>)this; 
     } 
    } 
} 

(с очевидными реализаций Equals, Hashcode, ToString)

Где вы раньше были операции, которые будут либо возвращать результат тип T или исключение, они вернут результат Try<T> (который был бы либо Success<T>, либо Fail<T>), и не выбрасывал бы, например:

class Test { 
    public static void main(String[] args) { 
     Try<String> r = ratio(2.0, 3.0).flatMap(Test::asString); 
    } 

    static Try<Double> ratio(double a, double b) { 
     if (b == 0) { 
      return new Try.Fail<Double>("Divide by zero"); 
     } else { 
      return new Try.Success<Double>(a/b); 
     } 
    } 

    static Try<String> asString(double d) { 
     if (Double.isNaN(d)) { 
      return new Try.Fail<String>("NaN"); 
     } else { 
      return new Try.Success<String>(Double.toString(d)); 
     } 
    } 
} 

I.e. вместо исключения исключения вы возвращаете значение Fail<T>, которое обертывает ошибку. Затем вы можете выполнить операции, которые могут завершиться неудачей, используя метод flatMap. Должно быть ясно, что после возникновения ошибки он будет замыкать любые последующие операции - в приведенном выше примере, если ratio возвращает Fail, то asString не вызывается и ошибка распространяется непосредственно до конечного результата r.

Берем пример, при таком подходе будет выглядеть следующим образом:

class ProjectService throws NotFoundException { 
    Try<DbProject> getProject(String id) { 
     DbProject p = ... // some hibernate code 
     if(p == null) { 
      return new Try.Fail<DbProject>("Failed to create DbProject"); 
     } 

     return new Try.Succeed<DbProject>(p); 
    } 
} 

Преимущество над сырья исключениями это немного больше компонуемы и позволяет, например, для вас карту (например, Stream.map) отказоустойчивая функция над набором значений и заканчивается сборкой «Неудачи и успехи». Если вы использовали исключения, первое исключение закончило бы всю операцию, и вы потеряли бы все результаты.

Одна из недостатков заключается в том, что вы должны использовать Try возвращаемые типы до конца стека вызовов (в некоторой степени, как проверенные исключения). Другим является то, что, поскольку Java не имеет встроенной поддержки монады (al la Haskell & Scala), тогда flatMap'ing может получить немного подробный. Например что-то вроде:

try { 
    A a = f(x); 
    B b = g(a); 
    C c = h(b); 
} catch (... 

где е, ж, з может бросить, становится вместо этого:

Try<C> c = f(x).flatMap(a -> g(a)) 
       .flatMap(b -> h(b)); 

Вы можете обобщают выше реализации, делая ошибки введите общий параметр E (вместо строки), поэтому он становится Try<T, E>. будет ли это полезно, зависит от ваших требований - я никогда не нуждался в нем.

У меня есть более полная версия here, в качестве альтернативы библиотеки Javaslang и FunctionalJava предлагают свои собственные варианты.

Смежные вопросы