【问题标题】:How to force or lead adapters to throw specific exceptions in Hexagonal/ Ports and Adapters Architecture in Java?如何强制或引导适配器在 Java 中的 Hexagonal/ Ports and Adapters Architecture 中抛出特定异常?
【发布时间】:2021-10-20 13:43:02
【问题描述】:

在实践中尝试在 Java 中实现 Hexagonal/Ports-and-Adapters 架构时,我在集成异常处理方面遇到了问题。

说有一个 RentBookPort 二级端口接口之类的

public interface RentBookPort {

    void rentBook(String isbn);
}

所以这个接口清楚地指定了端口合约,以通过给定的 isbn 租一本书。但是,如果我特别希望将不存在的书作为 EntityNotFoundException 或违反业务规则作为 InvalidInputException 抛出(即该书已被其他人租用)怎么办? 这是(对我而言)一个强有力的合同声明,该端口的每个辅助适配器都应该遵守,可能是真正的 db impl 或 mock impl。如何强制/领导端口接口的实现者这样做?

此外,如果我更改返回值或输入参数的合同,我会自动将编译问题作为警告信号。领先的开发人员是否有类似的技术/方式/技巧/模式来实现预期抛出的异常?

我搜索并找到最好的建议是 Joshua Bloch 的 Effective Java 中的第 56 项(“为所有公开的 API 元素编写 doc cmets”)和第 74 项(“记录每个方法引发的所有异常”)。比如:

public interface RentBookPort {

    /**
     * Rent a book for a given ISBN.
     * 
     * @param isbn
     * 
     * @throws EntityNotFoundException if no book for given ISBN can be found
     * @throws InvalidInputException   if the book is already rented by someone else
     */
    void rentBook(String isbn);
}

但这真的是我能做的最好的吗,利用 javadoc 并编写独立于适配器的端口接口验收测试以确保符合正确的异常抛出?

【问题讨论】:

  • 当你说你想抛出 InvalidInputException 一个“不可处理的 ISBN 号”时,你的意思是 String 不是一个格式良好的 ISBN 吗?换句话说,它是否可以通过仅查看字符串本身而无需其他上下文来验证?
  • @TimMoore:谢谢,好点子,isbn 只能通过查看字符串本身才有效,为此设计 InvalidInputException 是一个糟糕的选择/示例。在 DDD 术语中,最好引入一个值类型 ISBN,它将封装 ISBN 的概念,而不是使其成为适配器 impl 指定的东西。如果这本书已被其他人租用,我将示例更改为使用第二个例外,以使我的示例更清晰。

标签: java exception domain-driven-design clean-architecture hexagonal-architecture


【解决方案1】:

你可以在函数定义中将throws设为你。

考虑这个示例代码:

class EntityNotFoundException extends IllegalStateException {}
class InvalidInputException extends IllegalStateException {}

public interface RentBookPort {
    void rentBook(String isbn) throws EntityNotFoundException, InvalidInputException;
}


class RentBookPortImpl implements RentBookPort {

    @Override
    public void rentBook(String isbn) throws EntityNotFoundException, InvalidInputException {
        if (1 == 1) throw new EntityNotFoundException();
        if (1 == 2) throw new InvalidInputException();
        
        return;
    }

}

【讨论】:

  • 实际上这个问题不是关于“如何从接口方法实现中抛出不同的异常?”。这是基本的 Java 开箱即用功能。我面临的问题是“我如何确保实现我的接口的人在我设计接口时想要的情况下准确地抛出异常?”换句话说,“我希望在端口接口给出 x 的情况下抛出异常 y。我如何确保/质量检查每个适配器实现的这种行为?”
【解决方案2】:

我如何强制/领导端口接口的实现者这样做?

您不能真正强制接口的实现者表现得像指定的接口一样。实现者可能会忽略您的 javadoc 并返回 null 而不是抛出 EntityNotFoundException。但是对于所有接口来说都是同样的问题。

强制实现者遵循某些规则的唯一方法是指定模板方法。

public abstract class RentBookPort {
    
    public final BookRental rentBook(String isbn) {
        Book book = findBook(isbn);
        
        if(book == null) {
            throw new EntityNotFoundException();
        }
        
        return doRentBook(book);
    }
    
    protected abstract Book findBook(String isbn);
    protected abstract BookRental doRentBook(Book book);
}

但这需要实现者的灵活性。

您可以提供抽象的单元测试来指导实施者。

public abstract class  AbstractRentBookPortTest {

    private BookRentPort port;
    
    @Before
    public void setup() {
        port = createBookRentPort();
    }

    @Test
    void testRentNonExistentBook() {
        String nonExistentBook = "978-0-13-449416-6"
        ensureBookDoesNotExist(nonExistentBook);
        
        assertThrows(EntityNotFoundException.class, () => port.rentBook(nonExistentBook));
        
    }

    protected void ensureBookDoesNotExist(String isbn);
}

最后你只能通过集成测试来了解实现者是否履行了合同。

PS:为了使您的代码更加健壮,您可以引入一个 ISBN 类,以确保字符串是有效的 ISBN(在其构造函数中验证)。因此,当您获得 ISBN 实例时,您可以确定它是有效的,否则无法创建它。

【讨论】:

    【解决方案3】:

    “我怎样才能确保实现我的界面的人是 在设计时我打算的情况下完全抛出异常 界面?”

    “我希望在端口给出 x 的情况下抛出异常 y 界面。我如何确保/质量检查每个人的这种行为 适配器实现?”

    您必须在端口插入适配器并对其进行测试。测试异常抛出就像测试行为一样。

    例如:

    Given the isbn "x" doesn't exist
    
    When I rent a book with isbn "x"
    
    Then an EntityNotFoundException should be thrown
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-01-17
      • 2016-12-15
      • 1970-01-01
      • 2017-02-10
      • 2010-12-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多