简体   繁体   English

在Springboot应用程序中使用monad捕获异常

[英]Using monads in Springboot application to catch exceptions

So our project back-end is a Java 8 Springboot application, springboot allows you to do some stuff really easily. 因此,我们的项目后端是Java 8 Springboot应用程序,springboot允许您真正轻松地完成一些工作。 ex, request validation: 例如,要求验证:

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

When this constraint is not meet, spring (springboot?) actually throws a validation exception, as such, we catch it somewhere in the application and construct a 404 (Bad Request) response for our application. 当不满足此约束时,spring(springboot?)实际上会引发验证异常,因此,我们在应用程序中的某个位置捕获了该异常,并为我们的应用程序构造了404(错误请求)响应。

Now, given this fact, we kinda followed the same philosophy throughout our application, that is, on a deeper layer of the application we might have something like: 现在,鉴于这一事实,我们在整个应用程序中都遵循相同的理念,也就是说,在应用程序的更深层中,我们可能会遇到以下情况:

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

      return p;
  }
}

And again we catch this exception on a higher level, and construct another 404 for the client. 同样,我们在更高级别上捕获了此异常,并为客户端构造了另一个404。

Now, this is causing a few problems: 现在,这引起了一些问题:

  1. The most important one: Our error tracing stops being useful, we cannot differentiate (easily) when the exception is important, because they happen ALL the time, so if the service suddenly starts throwing errors we would not notice until it is too late. 最重要的一点:我们的错误跟踪不再有用,当异常很重要时,我们无法(轻松地)进行区分,因为它们一直发生,因此,如果服务突然开始抛出错误,直到为时已晚,我们才会注意到。

  2. Big amount of useless logging, on login requests for example, user might mistyped his password, and we log this and as a minor point: our analytics cannot help us determine what we are actually doing wrong, we see a lot of 4xx's but that is what we expect. 大量无用的日志记录,例如在登录请求时,用户可能输入了错误的密码,因此我们将其记录下来,这是一个次要点:我们的分析无法帮助我们确定我们实际上在做错什么,我们看到了很多4xx,但这是我们的期望。

  3. Exceptions are costly, gathering the stack trace is a resource intensive task, minor point at this moment, as the service scales up with would become more of a problem. 异常的代价很高,收集堆栈跟踪信息是一项资源密集型任务,这是目前的一个小问题,因为随着服务规模的扩大,这将成为一个更大的问题。

I think the solution is quite clear, we need to make an architectural change to not make exceptions part of our normal data flow, however this is a big change and we are short on time, so we plan to migrate over time, yet the problem remains for the short term. 我认为解决方案非常明确,我们需要进行架构更改,以不将异常作为正常数据流的一部分,但这是一个很大的更改,而且我们的时间很短,因此我们计划随时间迁移,但是问题是短期内仍然存在。

Now, to my actual question: when I asked one of our architects, he suggested the use of monads (as a temporal solution ofc), so we don't modify our architecture, but tackle the most contaminating endpoints (ex. wrong login) in the short term, however I'm struggling with the monad paradigm overall and even more in java, I really have no idea on how to apply it to our project, could you help me with this? 现在,我的实际问题是:当我问一位建筑师时,他建议使用monads(作为c的暂时解决方案),因此我们不修改我们的体系结构,而是处理最污染的端点(例如错误的登录)。在短期内,但是我一直在为monad范式而苦苦挣扎,甚至在Java中也是如此,我真的不知道如何将其应用于我们的项目,您能帮我这个忙吗? some code snippets would be really good. 一些代码片段会非常好。

TL:DR: If you take a generic spring boot application that throws errors as a part of its data flow, how can you apply the monad pattern to avoid login unnecessary amount of data and temporarily fix this Error as part of data flow architecture. TL:DR:如果您使用的是一个普通的Spring Boot应用程序,该应用程序在其数据流中抛出错误,那么如何应用monad模式以避免登录不必要的数据量,并将此Error临时修复为数据流体系结构的一部分。

The standard monadic approach to exception handling is essentially to wrap your result in a type that is either a successful result or an error. 异常处理的标准monadic方法实质上是将结果包装为成功结果或错误类型。 It's similar to the Optional type, though here you have an error value instead of an empty value. 它类似于Optional类型,尽管在这里您有一个错误值而不是一个空值。

In Java the simplest possible implementation is something like the following: 在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;
        }
    }
}

(with obvious implementations for equals, hashCode, toString) (对于equals,hashCode,toString具有明显的实现)

Where you previously had operations that would either return a result of type T or throw an exception, they would return a result of Try<T> (which would either be a Success<T> or a Fail<T> ), and would not throw, eg: 先前您有可能返回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));
        }
    }
}

Ie instead of throwing an exception you return a Fail<T> value which wraps the error. 即,您不会抛出异常,而是返回Fail<T>值来包装错误。 You can then compose operations which might fail using the flatMap method. 然后,您可以使用flatMap方法编写可能会失败的操作。 It should be clear that once an error occurs it will short-circuit any subsequent operations - in the above example if ratio returns a Fail then asString doesn't get called and the error propagates directly through to the final result r . 应该清楚的是,一旦发生错误,它将短路任何后续操作-在上面的示例中,如果ratio返回Fail则不会调用asString ,并且错误会直接传播到最终结果r

Taking your example, under this approach it would look like this: 以您的示例为例,在这种方法下,它看起来像这样:

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);
  }
}

The advantage over raw exceptions is it's a bit more composable and allows, for example, for you to map (eg Stream.map) a fail-able function over a collection of values and end up with a collection of Fails and Successes. 与原始异常相比,它的优点是更具可组合性,例如,允许您在值的集合上映射(例如Stream.map)可失败的函数,并最终导致失败和成功的集合。 If you were using exceptions then the first exception would fail the entire operation and you would lose all results. 如果使用异常,则第一个异常将使整个操作失败,并且将丢失所有结果。

One downside is that you have to use Try return types all the way down your call stack (somewhat like checked exceptions). 缺点之一是您必须在调用堆栈中一直使用Try返回类型(有点像检查异常)。 Another is that since Java doesn't have built-in monad support (al la Haskell & Scala) then the flatMap'ing can get slightly verbose. 另一个原因是,由于Java没有内置的monad支持(例如la Haskell和Scala),因此flatMap的功能可能会有些冗长。 For example something like: 例如:

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

where f, g, h might throw, becomes instead: f,g,h可能抛出的位置变为:

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

You can generalise the above implementation by making the error type an generic parameter E (instead of String), so it then becomes Try<T, E> . 您可以通过使错误类型成为通用参数E (而不是String)来概括上述实现,因此它变为Try<T, E> whether this is useful depends on your requirements - I've never needed it. 这是否有用取决于您的要求-我从不需要它。

I have a more fully-implemented version here , alternatively the Javaslang and FunctionalJava libraries offer their own variants. 在这里有一个更完整的版本,或者Javaslang和FunctionalJava库提供了它们自己的变体。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM