用Quarkus實現乾淨清晰的Clean架構 - Sourced Blog

banq發表於2020-03-24

Quarkus迅速成為一個不容忽視的框架,因此,我決定再次嘗試一下,以檢視在編寫Quarkus應用程式時可以在多大程度上堅持clean Architecture(CA)原則。
我的起點是一個基本的Maven專案,該專案在執行CA時具有用於CRUD REST應用程式的5個標準模組:
  • domain:領域實體和這些實體的閘道器介面
  • app-api:應用程式的用例介面
  • app-impl:使用領域來實現那些用例。取決於app-api和domain。
  • infra-persistence:使用資料庫API實現域定義的閘道器。取決於domain。
  • infra-web:使用REST將用例暴露於外界。取決於app-api。

此外,我們將建立一個main-partition模組,該模組將用作應用程式的可部署工件。
當您要使用Quarkus時,要做的第一件事是將BOM新增到專案的父POM中。此BOM將管理您將使用的依賴項的所有版本。您還需要在外掛管理中為maven專案配置標準外掛,例如compile和surefire外掛。當我們將使用Quarkus時,您還可以在外掛管理中在此處配置Quarkus外掛。最後但並非最不重要的一點是,您將配置一個外掛來為每個模組執行(如所示<build><plugins>...</plugins></build>),即Jandex外掛。由於Quarkus使用CDI,因此Jandex外掛會向每個模組新增一個索引檔案,該檔案包含該模組中使用的每個註釋以及指向其使用位置的連結。這將使使用CDI的生活更加輕鬆,並減少以後的工作量。
現在已經構建了基本結構,我們可以從構建功能正常的應用程式開始。為此,我們首先要確保main-partition建立了一個可執行的Quarkus應用程式。Quarkus提供的每個快速入門示例中都可以找到這種機制。
首先,將構建配置為使用Quarkus外掛:

<build>
  <plugins>
    <plugin>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-maven-plugin</artifactId>
      <executions>
        <execution>
          <goals>
            <goal>build</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

接下來,除了quarkus-resteasy和quarkus-jdbc-mysql依賴關係之外,您還需要嚮應用程式的每個模組新增依賴關係。您可以將最後一個依賴項更改為您選擇的資料庫(請記住,如果以後要使用本機路由,則不能使用像H2這樣的嵌入式資料庫)。
(可選)您可以新增一個配置檔案,該配置檔案稍後可用於構建本機應用程式。這確實需要您在開發平臺上擁有其他工具(GraalVM native-image和XCode(如果使用的是OSX))。

<profiles>
  <profile>
    <id>native</id>
    <activation>
      <property>
        <name>native</name>
      </property>
    </activation>
    <properties>
      <quarkus.package.type>native</quarkus.package.type>
    </properties>
  </profile>
</profiles>


現在,如果您mvn package quarkus:dev從專案根目錄執行,將有一個正在執行的Quarkus應用程式!您不會看到很多東西,因為我們還沒有任何控制器或內容。

新增REST控制器
對於本練習,我們將從外而內進行工作。首先,我們將建立一個REST控制器,該控制器將返回客戶資料(在此示例中,該資料僅包含名稱)。
為了使用JAX-RS API,您需要向infra-webPOM 新增一個依賴項:

<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-resteasy-jackson</artifactId>
</dependency>


基本的控制器程式碼如下所示:

@Path("/customer")
@Produces(MediaType.APPLICATION_JSON)
public class CustomerResource {
    @GET
    public List<JsonCustomer> list() {
        return getCustomers.getCustomer().stream()
                .map(response -> new JsonCustomer(response.getName()))
                .collect(Collectors.toList());
    }

    public static class JsonCustomer {
        private String name;

        public JsonCustomer(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }
    }


如果我們現在執行該應用程式,您將能夠呼叫http://localhost:8080/customer並Joe以JSON格式檢視。

新增用例
接下來,我們將新增一個用例和該用例的實現。在app-api我們定義以下使用情況:

public interface GetCustomers {
    List<Response> getCustomers();

    class Response {
        private String name;

        public Response(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }
    }
}

在本文中,app-impl我們將為該介面建立一個基本實現。

@UseCase
public class GetCustomersImpl implements GetCustomers {
    private CustomerGateway customerGateway;

    public GetCustomersImpl(CustomerGateway customerGateway) {
        this.customerGateway = customerGateway;
    }

    @Override
    public List<Response> getCustomers() {
        return Arrays.asList(new Response("Jim"));
    }
}

為了讓CDI看到GetCustomersImplBean,您需要使用UseCase如下定義的自定義註釋。您還可以使用標準ApplicationScoped和Transactional批註,但是建立自己的批註可以使您對這些批註進行邏輯分組,並使實現程式碼與CDI之類的框架脫鉤。

@ApplicationScoped
@Transactional
@Stereotype
@Retention(RetentionPolicy.RUNTIME)
public @interface UseCase {
}


為了使用CDI註解,你必須以下依存增加的POM app-impl除了依賴於app-api和domain。

<dependency>
  <groupId>jakarta.enterprise</groupId>
  <artifactId>jakarta.enterprise.cdi-api</artifactId>
</dependency>
<dependency>
  <groupId>jakarta.transaction</groupId>
  <artifactId>jakarta.transaction-api</artifactId>
</dependency>


現在,我們需要更改REST控制器以使用app-api用例。

...
private GetCustomers getCustomers;

public CustomerResource(GetCustomers getCustomers) {
    this.getCustomers = getCustomers;
}

@GET
public List<JsonCustomer> list() {
    return getCustomers.getCustomer().stream()
            .map(response -> new JsonCustomer(response.getName()))
            .collect(Collectors.toList());
}
...


如果現在執行該應用程式並呼叫http://localhost:8080/customer,您將看到JimJSON格式。

定義和實施領域
因此,接下來:業務領域。在domain這裡是非常簡單,它由Customer和閘道器介面來獲得客戶。

public class Customer {
    private String name;

    public Customer(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}
public interface CustomerGateway {
    List<Customer> getAllCustomers();
}


在開始使用閘道器之前,我們還需要提供閘道器的實現。在中infra-persistence,我們將提供這樣的介面。
對於此實現,我們將在Quarkus中使用JPA支援,並使用Panache框架使生活更輕鬆。infra-persistence除了之外,您還必須新增以下依賴項domain:

<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-hibernate-orm-panache</artifactId>
</dependency>


首先,我們為客戶定義JPA實體。

@Entity
public class CustomerJpa {
    @Id
    @GeneratedValue
    private Long id;
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

使用Panache,您可以選擇擴充套件實體PanacheEntity或使用儲存庫/ DAO模式。我不是真的很喜歡ActiveRecord模式,因此我選擇使用儲存庫,但是選擇取決於您。

@ApplicationScoped
public class CustomerRepository implements PanacheRepository<CustomerJpa> {
}


現在我們有了JPA實體和儲存庫,我們可以實現Customer閘道器了。

@ApplicationScoped
public class CustomerGatewayImpl implements CustomerGateway {
    private CustomerRepository customerRepository;

    @Inject
    public CustomerGatewayImpl(CustomerRepository customerRepository) {
        this.customerRepository = customerRepository;
    }

    @Override
    public List<Customer> getAllCustomers() {
        return customerRepository.findAll().stream()
                .map(c -> new Customer(c.getName()))
                .collect(Collectors.toList());
    }
}


現在,我們可以在用例實現中更改程式碼以使用閘道器。

...
private CustomerGateway customerGateway;

@Inject
public GetCustomersImpl(CustomerGateway customerGateway) {
    this.customerGateway = customerGateway;
}

@Override
public List<Response> getCustomer() {
    return customerGateway.getAllCustomers().stream()
            .map(customer -> new GetCustomers.Response(customer.getName()))
            .collect(Collectors.toList());
}
...

我們還不能執行我們的應用程式,因為我們現在需要為Quarkus應用程式配置必要的永續性引數。在src/main/resources/application.properties中main-partition,新增以下引數:

quarkus.datasource.url=jdbc:mysql://localhost/test
quarkus.datasource.driver=com.mysql.cj.jdbc.Driver
quarkus.hibernate-orm.dialect=org.hibernate.dialect.MySQL8Dialect
quarkus.datasource.username=root
quarkus.datasource.password=root
quarkus.datasource.max-size=8
quarkus.datasource.min-size=2
quarkus.hibernate-orm.database.generation=drop-and-create
quarkus.hibernate-orm.sql-load-script=import.sql


如果現在執行該應用程式並呼叫http://localhost:8080/customer,您將看到Joe和JimJSON格式。現在,我們有了從REST到資料庫的完整應用程式。

更詳細測試點選標題見原文


 

相關文章