实现WordRepository适配器

在本节中,我们将使用流行的数据库库 JDBI 来实现 WordRepository 接口的 fetchWordByNumber() 方法,并使我们的失败集成测试通过。

六边形架构在第 9 章《六边形架构——解耦外部系统》中已经介绍过。像数据库这样的外部系统通过领域模型中的端口进行访问。特定于该外部系统的代码包含在适配器中。我们的失败测试使我们能够编写数据库访问代码以获取要猜测的单词。

在开始编写代码之前,我们需要进行一些数据库设计思考。对于当前任务,我们只需要注意将所有可用的猜测单词存储在一个名为 word 的数据库表中。该表将有两列:一个名为 word_number 的主键和一个名为 word 的五字母单词列。

让我们通过测试驱动来实现这一点:

  1. 运行测试,发现 word 表不存在:

    image 2025 01 12 21 07 56 789
    Figure 1. Figure 14.9 – Table not found
  2. 通过在数据库中创建一个 word 表来纠正这个问题。我们使用 psql 控制台运行 SQL 创建表命令:

    create table word (word_number int primary key, word char(5));
  3. 再次运行测试。错误变为显示我们的 ciuser 用户权限不足:

    image 2025 01 12 21 09 21 113
    Figure 2. Figure 14.10 – Insufficient permissions
  4. 我们通过在 psql 控制台运行 SQL grant 命令来纠正这个问题:

    grant select, insert, update, delete on all tables in schema public to ciuser;
  5. 再次运行测试。错误信息显示我们没有从数据库表中读取到 word

    image 2025 01 12 21 11 40 904
    Figure 3. Figure 14.11 – Word not found

访问数据库

在设置好数据库部分后,我们可以开始添加访问数据库的代码。第一步是添加我们将使用的数据库库——JDBI。为了使用它,我们必须将 jdbi3-core 依赖项添加到我们的 gradle.build 文件中:

dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
    testImplementation 'org.assertj:assertj-core:3.22.0'
    testImplementation 'org.mockito:mockito-core:4.8.0'
    testImplementation 'org.mockito:mockito-junit-jupiter:4.8.0'
    testImplementation 'com.github.database-rider:rider-core:1.35.0'
    testImplementation 'com.github.database-rider:rider-junit5:1.35.0'

    implementation 'org.postgresql:postgresql:42.5.0'
    implementation 'org.jdbi:jdbi3-core:3.34.0'
}

代码本身如 JDBI 文档中所述,可以在此处找到: https://jdbi.org/#_queries

按照以下步骤访问数据库:

  1. 我们在类的构造函数中创建一个 jdbi 对象:

    public class WordRepositoryPostgres implements WordRepository {
       private final Jdbi jdbi;
    
       public WordRepositoryPostgres(DataSource dataSource) {
           jdbi = Jdbi.create(dataSource);
       }
    }

    这使我们能够访问 JDBI 库。我们通过这种方式安排,使 JDBI 能够访问我们传递给构造函数的任何 DataSource

  2. 我们添加 JDBI 代码以向数据库发送 SQL 查询,并获取与作为方法参数提供的 wordNumber 对应的单词。首先,我们添加将使用的 SQL 查询:

    private static final String SQL_FETCH_WORD_BY_NUMBER = "select word from word where word_number=:wordNumber";
  3. 我们将 JDBI 访问代码添加到 fetchWordByNumber() 方法中:

    @Override
    public String fetchWordByNumber(int wordNumber) {
       String word = jdbi.withHandle(handle -> {
           var query = handle.createQuery(SQL_FETCH_WORD_BY_NUMBER);
           query.bind("wordNumber", wordNumber);
           return query.mapTo(String.class).one();
       });
       return word;
    }
  4. 再次运行测试

image 2025 01 12 21 16 47 207
Figure 4. Figure 14.12 – Test passing

我们的集成测试现在通过了。适配器类已从数据库读取单词并返回。

实现GameRepository

同样的过程用于测试驱动 highestWordNumber() 方法,并创建实现 GameRepository 接口的其他数据库访问代码的适配器。这些代码的最终版本可以在 GitHub 上查看,并附有注释以探讨数据库测试中的一些问题,例如如何避免由存储数据引起的测试失败。

在测试驱动 GameRepository 接口的实现代码时,需要一个手动步骤。我们必须创建一个 game 表。

psql 中,输入以下内容:

CREATE TABLE game (
    player_name character varying NOT NULL,
    word character(5),
    attempt_number integer DEFAULT 0,
    is_game_over boolean DEFAULT false
);