当前位置: 首页 > news >正文

建网站上海商河网站建设公司

建网站上海,商河网站建设公司,网站备案 几年,辽宁建设工程信息网联合体投标用模块化整体架构编写的代码实际上是什么样的#xff1f;借助 Spring Boot 和 DDD#xff0c;我们踏上了编写可维护和可演化代码的旅程。 当谈论模块化整体代码时#xff0c;我们的目标是以下几点#xff1a; 应用程序被组织成模块。每个模块解决业务问题的不同部分。模块…用模块化整体架构编写的代码实际上是什么样的借助 Spring Boot 和 DDD我们踏上了编写可维护和可演化代码的旅程。 当谈论模块化整体代码时我们的目标是以下几点 应用程序被组织成模块。每个模块解决业务问题的不同部分。模块是松散耦合的。不同模块之间没有循环依赖关系因为它会导致代码难以维护。完整的应用程序在运行时部署为单个单元。这是整体部分。模块的公共接口暴露给其他模块的行为是灵活的并且可以原子地更改。与微服务不同当我们需要更改模块的公共接口时使用该接口的其他模块可以一起更改并推出。 边界的确定仍然很重要。不同之处在于模块导致边界错误的成本比微服务要低得多。因此在项目开始时当对业务问题的共同理解较低时从整体模块开始比从微服务开始更安全。 我们如何识别模块边界根据我的经验领域驱动设计的模式是解决这个问题的最佳工具之一。 业务问题 让我们来模拟图书馆和图书借阅流程。这里是需求图书馆和图书借阅流程。这里是要求图书借阅流程。以下是要求 图书馆有数千本书。图书馆有成千上万本书。同一本书可能有多个副本。同一本书可以有多个副本。在纳入图书馆之前每本书的背面或其中一页尾页都会印上一个条形码。每本书的背面或其中一页尾部都有一个条形码。图书每本书的背面或其中一页尾部都有一个条形码。该条形码编号可唯一标识书本背面或其中一页尾部的条形码。该条形码编号可唯一标识图书。图书馆读者可以在有书的情况下借阅图书。通常读者在图书馆找到该书然后到流通处借阅。有时读者可以直接到服务台按书名借书。通常情况下读者在图书馆找到图书然后到流通处借阅。有时读者可以直接到服务台按书名借书。通常情况下读者在图书馆找到图书后到流通台借阅。有时读者可以直接到服务台按书名查找图书然后到流通台借阅。有时读者可以直接到服务台按书名 图书馆 查找图书然后到流通台借阅。有时读者可以直接到服务台按书名.desk 要求借书然后到流通台借出。有时读者可以直接到服务台按书名要求借书然后到流通台按书名借书。图书的借出期固定为两周。借书时读者可以去借书处也可以把书扔到图书投放区。 划分子域 让我们把这个图书馆域分解成几个子域。其中一个子域是图书的借阅过程。这个子域的主要行为者是想要借书的读者。 另一个子域是图书盘点子域即图书盘点以及添加和删除带有条形码的图书。这个子域的主要角色是图书管理员或条形码管理员。该子域的主要参与者是图书管理员或管理员。 还可以确定更多的子域--如读者管理在允许读者借阅图书前对读者进行身份识别和验证、图书报告和分析、向读者发出通知等。但由于我们没有这方面的要求所以暂时不考虑这些子域。已确定的子域--如读者管理在允许读者借阅图书前对读者进行身份识别和验证、图书报告和分析、向读者发出通知等。但由于我们没有这方面的需求所以暂时不考虑。 请注意这些子域是我们第一次尝试对需求进行细分。它可能是正确的也可能是完全错误的。更重要的是我们要根据目前对问题的理解进行尝试。随着时间的推移我们会有更多的了解我们可能需要重组子域。这可能是正确的也可能是完全错误的。更重要的是我们要根据目前对问题的理解进行尝试。随着时间的推移我们会获得更多的见解我们可能需要重组子域。 构建解决方案 对于我们发现的每个子域我们通过设计一个有界上下文来逐个解决子域问题。这些有界上下文也就是我们的模块化单体应用中的模块。 src/main/javajava └── example ├── borrow │   ├── LoanLoan │   ├── LoanController │   ├── () LoanDto │   ├── () LoanManagement │   ├── LoanMapper       │   ├── LoanRepository │   └── LoanWithBookDto └── inventoryinventory ├── Book ├── BookController ├── () BookDto ├── () BookManagement ├── BookMapper └── BookRepository 图书库存有界上下文图书库存有界上下文 让我们通过子域建模来设计图书库存的有界上下文。我们可以借助聚合模式来实现这一目的。 聚合是数据存储传输的基本要素--您需要加载或保存整个聚合。事务不应跨越聚合边界。 在这个子域中最需要持久化的是 图书。在 Java 中我们可以将聚合建模为 JPA 实体。 Entity Getter NoArgsConstructor Table(uniqueConstraints UniqueConstraint(columnNames {barcode})) class Book { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; private String title; Embedded private Barcode inventoryNumber; private String isbn; Embedded AttributeOverride(name name, column Column(name author)) private Author author; Enumerated(EnumType.STRING) private BookStatus status; Version private Long version; public Book(String title, Barcode inventoryNumber, String isbn, Author author) { this.title title; this.inventoryNumber inventoryNumber; this.isbn isbn; this.author author; this.status BookStatus.AVAILABLE; } public boolean isAvailable() { return BookStatus.AVAILABLE.equals(this.status); } public boolean isIssued() { return BookStatus.ISSUED.equals(this.status); } public Book markIssued() { if (this.status.equals(BookStatus.ISSUED)) { throw new IllegalStateException(Book is already issued!); } this.status BookStatus.ISSUED; return this; } public Book markAvailable() { this.status BookStatus.AVAILABLE; return this; } public record Barcode(String barcode) { } public record Author(String name) { } public enum BookStatus { AVAILABLE, ISSUED } } 源码 GitHub. 聚合 图书聚合由图书实体和三个值对象条形码、BookStatus 和作者组成。我们没有把作者变成另一个实体因为我们没有围绕它的任何业务需求。在现实世界中我们应该咨询领域专家了解未来是否会有需求并据此决定实体和值对象。 在这个聚合中Book 也充当聚合根这意味着对这个聚合的任何更改如修改 Book 的状态都必须只通过 Book 实体进行并且仅限于模块本身。就代码而言这意味着不应有一个公共设置器方法 setStatus() 可供应用程序的其他模块访问。 请注意上述实现不仅包含状态还包含行为--markIssued()、markAvailable()。在领域模型中包含行为非常重要否则就会变成贫血模型。 接下来我们需要一个存储库来与数据库交互。有了 Spring Data这就变得轻而易举了 interface BookRepository extends JpaRepositoryBook, Long { Optional findByIsbn(String isbn); Optional findByInventoryNumber(Book.Barcode inventoryNumber); List findByStatus(Book.BookStatus status); } 添加了一些常用搜索方法可通过国际标准书号、条形码和状态查找图书。请注意该资源库接口的可见性是包私有的而不是公共的。 接下来我们将通过 BookManagement 服务创建模块的公共接口。 Transactional Service RequiredArgsConstructor public class BookManagement { private final BookRepository bookRepository; private final BookMapper mapper; public BookDto addToInventory(String title, Book.Barcode inventoryNumber, String isbn, String authorName) { var book new Book(title, inventoryNumber, isbn, new Book.Author(authorName)); return mapper.toDto(bookRepository.save(book)); } public void removeFromInventory(Long bookId) { var book bookRepository.findById(bookId) .orElseThrow(() - new IllegalArgumentException(Book not found!)); if (book.issued()) { throw new IllegalStateException(Book is currently issued!); } bookRepository.deleteById(bookId); } public void issue(String barcode) { var inventoryNumber new Book.Barcode(barcode); var book bookRepository.findByInventoryNumber(inventoryNumber) .map(Book::markIssued) .orElseThrow(() - new IllegalArgumentException(Book not found!)); bookRepository.save(book); } public void release(String barcode) { var inventoryNumber new Book.Barcode(barcode); var book bookRepository.findByInventoryNumber(inventoryNumber) .map(Book::markAvailable) .orElseThrow(() - new IllegalArgumentException(Book not found!)); bookRepository.save(book); } Transactional(readOnly true) public Optional locate(Long id) { return bookRepository.findById(id) .map(mapper::toDto); } Transactional(readOnly true) public List issuedBooks() { return bookRepository.findByStatus(Book.BookStatus.ISSUED) .stream() .map(mapper::toDto) .toList(); } } 有几点需要注意。BookManagement 服务返回的是 DTO 而不是图书实体。它使用 MapStruct 驱动的映射器将实体转换为 DTO反之亦然。通过在服务层只返回 DTO我们保护了领域模型实体不会泄漏到控制器层和表现层。对于小型项目来说这似乎有些矫枉过正但对于相当大的项目来说未来的自己会感谢你将域限制在服务层内。 其次除了 DTO 之外BookManagement 是其他模块唯一可以访问的类。为此我们将所有其他类都封装为私有类。还有其他方法可以实现这一点我们稍后再讨论。 最后我们可以通过为客户端创建 REST API 来完成有界上下文的实现。这就是 BookController 类。我们只依赖服务层而不注入存储库。这样可以确保 API 始终按照服务层的保证返回 DTO。 RestController RequiredArgsConstructor class BookController { private final BookManagement books; PostMapping(/books) ResponseEntity addBookToInventory(RequestBody AddBookRequest request) { var bookDto books.addToInventory(request.title(), new Barcode(request.inventoryNumber()), request.isbn(), request.author()); return ResponseEntity.ok(bookDto); } DeleteMapping(/books/{id}) ResponseEntity removeBookFromInventory(PathVariable(id) Long id) { books.removeFromInventory(id); return ResponseEntity.ok().build(); } GetMapping(/books/{id}) ResponseEntity viewSingleBook(PathVariable(id) Long id) { return books.locate(id) .map(ResponseEntity::ok) .orElse(ResponseEntity.notFound().build()); } GetMapping(/books) ResponseEntityList viewIssuedBooks() { return ResponseEntity.ok(books.issuedBooks()); } record AddBookRequest(String title, String inventoryNumber, String isbn, String author) { } } 通过 库存有界上下文我们已经满足了前面列出的前两个要求。 下面借阅有界上下文BC将满足其余要求。 借阅BC 借阅BC处理图书馆读者借出和借入图书的事务。它依赖于 库存 绑定上下文来检查图书的可用性并在图书可用的情况下发放读者所需的图书。 在这个子域中需要建模的概念是借书。领域专家告诉我们这个概念的术语是 借阅Loan。它是一个长期存在的实体会随着时间的推移经历不同的状态并且必须遵循业务规则。因此它将是这个有界上下文的聚合集合体。 Entity Getter Setter NoArgsConstructor public class Loan { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; private String bookBarcode; private Long patronId; private LocalDate dateOfIssue; private int loanDurationInDays; private LocalDate dateOfReturn; Enumerated(EnumType.STRING) private LoanStatus status; Version private Long version; Loan(String bookBarcode) { this.bookBarcode bookBarcode; this.dateOfIssue LocalDate.now(); this.loanDurationInDays 14; this.status LoanStatus.ACTIVE; } public static Loan of(String bookBarcode) { return new Loan(bookBarcode); } public boolean isActive() { return LoanStatus.ACTIVE.equals(this.status); } public boolean isOverdue() { return LoanStatus.OVERDUE.equals(this.status); } public boolean isCompleted() { return LoanStatus.COMPLETED.equals(this.status); } public void complete() { if (isCompleted()) { throw new IllegalStateException(Loan is not active!); } this.status LoanStatus.COMPLETED; this.dateOfReturn LocalDate.now(); } public enum LoanStatus { ACTIVE, OVERDUE, COMPLETED } } 请注意图书实体没有外键关系。相反我们在 借阅 模型中存储了分配给每本书的图书馆库存编号条形码。这是一个唯一标识符因此可以安全地用作参考。 这是允许领域模型驱动实体模型而不是相反的结果。通过不使用外键关系我们还避免了取值策略懒惰/急迫和级联策略带来的无数问题。在 Loan 和 Book 之间没有 JPA 多对一关系模型。它是在领域模型中直观定义的并由聚合不变式强制执行。 当然缺点是数据库不再能保护我们免受数据损坏。因此需要对应用层的实现进行测试。 让我们抵制寻求实体建模的冲动转而将领域建模作为构建解决方案的第一步。 接下来我们将看看借阅管理服务LoanManagement service有趣的事情就在这里发生。 Transactional Service RequiredArgsConstructor public class LoanManagement { private final LoanRepository loanRepository; private final BookManagement books; private final LoanMapper mapper; public LoanDto checkout(String barcode) { books.issue(barcode); var loan Loan.of(barcode); var savedLoan loanRepository.save(loan); return mapper.toDto(savedLoan); } public LoanDto checkin(Long loanId) { var loan loanRepository.findById(loanId) .orElseThrow(() - new IllegalArgumentException(No loan found)); books.release(loan.getBookBarcode()); loan.complete(); return mapper.toDto(loanRepository.save(loan)); } Transactional(readOnly true) public List activeLoans() { return loanRepository.findLoansWithStatus(LoanStatus.ACTIVE); } Transactional(readOnly true) public Optional locate(Long loanId) { return loanRepository.findById(loanId) .map(mapper::toDto); } } 首先要注意的是LoanManagement 服务依赖于 BookManagement 服务。在借出操作中需要发放图书。在签到操作中需要释放已签发的图书。 其次checkout 和 checkin 的实现根本不执行任何不变式检查。它们只需调用贷款聚合或图书管理服务的方法然后由这些方法执行不变性检查。这样LoanManagement 服务的实现就非常清晰易懂了。 最后与 BookManagement 类似该服务只返回 Loan DTO而不返回实体本身。 Borrow 边界上下文还包含在 LoanController 中实现的 REST API。实现过程非常简单可直接在 GitHub 上查看。 该项目包含 Springdoc 依赖项用于生成基于 Swagger 的文档可访问 http://localhost:8080/swagger-ui.html。 org.springdoc springdoc-openapi-starter-webmvc-ui ${springdoc-openapi-starter-webmvc-ui.version} 要启动应用程序请运行 mvn spring-boot:run。 源码 GitHub. 局限性 在讨论我们实施方案的局限性之前让我们先回顾一下我们的实施方案。 我们应用了 DDD 原则来构建模块化解决方案。 领域模型是包含数据和行为的真正聚合体。它们负责验证不变式。 代码是可测试的结构是模块化的希望也是易于理解的。 但还有一些地方可以改进。 有界上下文BC之间的紧密耦合 如前所述借用 BC与 库存 BC之间存在紧密耦合。如果 库存 BC 不可用在单体中不太可能那么 借用 BC就无法运行。 此外结账请求在一次事务中更新了 Loan 和 Book 两个聚合。这违反了在一个事务中只更新一个聚合的推荐做法。 和其他事情一样这也是一种权衡。作为一个单体应用程序我们处理的是单个数据库这允许我们更新多个聚合并保持实现简单。在下一篇博客中我们将看到一组新的需求将如何迫使我们尝试不同的解决方案。 有界上下文BC的独立测试 紧密耦合的直接后果是测试单个受限上下文BC借用需要处理所有从属上下文库存。 这一点在《借阅管理》LoanManagement的集成测试中很明显。借出测试必须断言借出图书的状态已更新为 ISSUED。同样签入测试也必须断言已归还图书的状态已更新为 AVAILABLE。不需要模拟或注入 BookManagement 服务就能测试签出行为这不是很好吗 Transactional SpringBootTest class LoanManagementIT { Autowired LoanManagement loans; Autowired BookManagement books; Test void shouldCreateLoanAndIssueBookOnCheckout() { var loanDto loans.checkout(13268510); assertThat(loanDto.status()).isEqualTo(LoanStatus.ACTIVE); assertThat(loanDto.bookBarcode()).isEqualTo(13268510); assertThat(books.locate(1L).get().status()).hasToString(ISSUED); } Test void shouldCompleteLoanAndReleaseBookOnCheckin() { var loan loans.checkin(10L); assertThat(loan.status()).isEqualTo(LoanStatus.COMPLETED); assertThat(books.locate(2L).get().status()).hasToString(AVAILABLE); } } 控制受限上下文BC的接口 如前所述每个有界上下文BC只公开供其他有界上下文BCDTO 和服务类使用的特定类。它们是上下文的接口。这可以通过控制类的可见性来实现。 遗憾的是这需要仔细和持续的监督。一不小心就会忘记并破坏规则例如新开发人员加入项目最终导致接口扩展。如果任其发展代码很快就会变得一团糟无法维护。使用类可见性还可以限制每个上下文的子包。 在理想情况下如果我们能使用测试来自动防止跨边界上下文包的非法访问那就再好不过了。 https://www.jdon.com/70712.html
http://wiki.neutronadmin.com/news/8541/

相关文章:

  • 牡丹区建设局网站沈阳新联会是什么组织做什么
  • 南京建设网站多少钱免费自建网站工具
  • logo网站设计论文龙岩属于哪里
  • 临沂网站建设制作中山网站优化排名
  • 昆明网站建设推广公司哪家好360网站弹窗推广怎么做的
  • 高端网站定做城乡建设住房建设厅官方网
  • 淘宝优惠劵网站怎么做沧州做网站哪家好
  • 濮阳佳杰网站建设巧用不对称做网站和彩票的同步开奖怎么做
  • 什么样的网站容易做seo线下推广的渠道和方法
  • wordpress添加文章页不显示网站怎么做优化百度能搜索到
  • html5网站开发技术小蜜蜂wordpress采集
  • 个人网站备案地址选择梧州市建设局官方网站
  • 运营网站是多少wordpress检测手机端
  • 网站seo诊断方案怎么做电影网站吗
  • git网站开发如何给自己公司做一个网站
  • 洛阳网站建设哪家公司好提供建立网站服务的公司
  • 浦东高端网站开发html网站免费下载
  • 邢台微信网站网站建设板块建议
  • 鲜花网站开发wordpress底部链接
  • h5网站用什么软件做出入成都最新通知今天
  • 网站建设与推广的步骤域名买好怎么开始做网站
  • 常州h5网站建设网站后台怎么上传图片产品
  • 大连做网站价钱邯郸seo推广
  • seo百度排名优化自己给网站做优化怎么做
  • 网站升级维护要多久做网站用什么服务器比较好
  • 怎么做网站服务器系统郑州网络营销学校
  • 网站广告设计怎么做制作网站公司多少钱
  • 网站备案名可以更改吗微信商城小程序定制
  • 泸县做网站公司西安公司排行榜
  • 深圳做企业网站哪家好免费软件下载网站入口