因此,我们的项目后端是Java 8 Springboot应用程序,springboot允许您真正轻松地完成一些工作。例如,要求验证:
class ProjectRequestDto {
@NotNull(message = "{NotNull.DotProjectRequest.id}")
@NotEmpty(message = "{NotEmpty.DotProjectRequest.id}")
private String id;
}
当不满足此约束时,spring(springboot?)实际上会引发验证异常,因此,我们在应用程序中的某个地方捕获了该异常,并为我们的应用程序构造了404(错误请求)响应。
现在,鉴于这一事实,我们在整个应用程序中都遵循相同的理念,也就是说,在应用程序的更深层中,我们可能会遇到以下情况:
class ProjectService throws NotFoundException {
DbProject getProject(String id) {
DbProject p = ... // some hibernate code
if(p == null) {
Throw new NotFoundException();
}
return p;
}
}
同样,我们在更高级别上捕获了此异常,并为客户端构造了另一个404。
现在,这引起了一些问题:
最重要的一个是:我们的错误跟踪不再有用,当异常很重要时,我们无法(轻松地)进行区分,因为它们一直在发生,因此,如果服务突然开始抛出错误,我们将不会注意到,直到为时已晚。
大量无用的日志记录,例如,在登录请求上,用户可能输入了错误的密码,我们将其记录下来,这是一个很重要的一点:我们的分析无法帮助我们确定我们实际上在做错什么,我们看到了很多4xx,但这就是我们的期望。
异常的代价很高,收集堆栈跟踪信息是一项资源密集型任务,这是目前的一个小问题,因为随着服务规模的扩大,这将成为一个更大的问题。
我认为解决方案非常清楚,我们需要进行架构更改,以不将异常作为正常数据流的一部分,但这是一个很大的更改,而且我们的时间很短,因此我们计划随时间迁移,但是问题是短期内仍然存在。
现在,我的实际问题是:当我问一位架构师时,他建议使用monads(作为c的临时解决方案),因此我们不修改架构,而是处理最污染的端点(例如错误的登录)。在短期内,但是我一直在为monad范式而苦苦挣扎,甚至在Java中都在挣扎,我真的不知道如何将其应用于我们的项目,您能帮我这个忙吗?一些代码片段会非常好。
TL:DR:如果您使用的是一个普通的Spring Boot应用程序,该应用程序在其数据流中抛出错误,那么如何应用monad模式以避免登录不必要的数据量,并将此Error临时修复为数据流体系结构的一部分。
处理异常的标准单子方法本质上是将结果包装为成功结果或错误类型。它与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>
(可能是aSuccess<T>
或a 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));
}
}
}
即,您不会抛出异常,而是返回一个Fail<T>
包装错误的值。然后,您可以使用flatMap方法编写可能会失败的操作。应该清楚的是,一旦发生错误,它将短路任何后续的操作-在上面的例子中,如果ratio
返回aFail
则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没有内置的monad支持(如la Haskell和Scala),因此flatMap的功能可能会有些冗长。例如:
try {
A a = f(x);
B b = g(a);
C c = h(b);
} catch (...
f,g,h可能抛出的位置变为:
Try<C> c = f(x).flatMap(a -> g(a))
.flatMap(b -> h(b));
您可以通过将错误类型设为通用参数E
(而不是String)来概括上述实现,然后变为Try<T, E>
。是否有用取决于您的要求-我从不需要它。
我在这里有一个更完整实现的版本,或者Javaslang和FunctionalJava库提供了它们自己的变体。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句