moduliths: 使用Spring Boot构建模块化单体架构的工具
Posted Java海
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了moduliths: 使用Spring Boot构建模块化单体架构的工具相关的知识,希望对你有一定的参考价值。
在设计应用程序时,我们目前处理两种 架构 方法:单体应用程序和 微服务 。虽然通常以相反的方法呈现,但在极端情况下,它们实际上形成了特定应用程序架构可以定位的范围的末端。小型系统的趋势受到以下事实的强烈推动:单体应用程序往往会随着时间的推移在架构上退化,即使——在它们生命的开始——定义了一个架构。随着时间的推移,架构违规行为会悄悄潜入项目中。随着系统变得更难改变,可演化性受到影响。
另一方面,微服务承诺更强大的分离手段,但同时引入了很多复杂性,因为即使是小型应用程序团队也必须应对 分布式系统 的挑战。
这个 repo 就像一个游乐场,可以尝试不同的方法来定义模块化的单体,这样就可以很容易地随着时间的推移保持模块化并及早发现违规行为。这将保持随着时间的推移修改和推进代码库的能力,并简化拆分系统以尝试将其部分提取到专用项目中的工作。
Moduliths 是基于 ArchUnit 的 Spring Boot 扩展,旨在实现以下目标:
[list]
[*]验证单体 Spring Boot 应用程序中各个逻辑模块之间的模块化结构。
防止循环依赖以及明确定义允许的对其他模块的依赖。验证对 API 包中公共组件的访问(基于约定,可定制)。
[*]引导单一 Spring Boot 应用程序的模块子集。
将 Spring Boot 的引导(组件扫描和自动配置的应用)限制为单个模块、一个模块及其直接依赖项或整个树。
[*]派生有关模块的 PlantUML 文档。
通过 Structurizr 将实际模块结构转换为 PlantUML 组件图,以便轻松包含在基于 Asciidoctor 的文档中。可以一次为单个模块及其协作者或整个系统呈现图表。
[/list]
架构代码差距
在软件项目中,架构设计决策和约束通常以某种方式定义,然后必须在代码库中实现。传统上,架构决策和实际情况之间的联系一直是命名约定,这些约定很容易产生分歧,并导致在代码库中实际实现的架构随着时间的推移而缓慢退化。我们希望探索架构和代码之间更强大的连接方式,甚至研究对框架和库的高级支持,例如允许对整个系统中的各个组件进行可测试性。
已经有多种技术试图从架构定义方面弥合这种差距,主要是通过尝试以可执行形式捕获架构定义(请参阅 jQAssistant 和 现有工具 )并验证代码库是否符合定义的约定。在这个操场上,我们将探索相反的方式:提供约定以及库和框架手段,直接在代码库中表达架构定义,主要目标有两个:
[list=1]
[*]让约定的验证更接近代码/开发人员 ——如果架构决策是由开发团队驱动的,那么在代码库中定义架构概念可能会感觉更自然。架构规则验证系统与代码库集成得越无缝,使用该系统的可能性就越大。可以由编译器验证的架构规则优先于通过执行测试验证的规则,而后者又优于通过专用构建步骤进行的验证。
[*]与现有工具集成——即使与现有工具结合使用,它也可能只是帮助他们提供开箱即用的通用架构规则,开发人员只需遵循约定或显式注释代码以触发实际验证。
[/list]
设计目标
[list]
[*]使开发人员能够编写架构上明显的代码,即提供在代码中表达架构概念的方法,以缩小两者之间的差距。
[*]提供方法来验证尽可能接近代码的定义的架构约束(通过编译器,通过测试或其他构建工具)。
[*]尽可能少的侵入性技术。即,我们更喜欢记录的约定而不是所需的类型依赖关系的注释。
[/list]
快速开始
[list=1]
[*]创建一个简单的 Spring Boot 应用程序(例如通过 Spring Initializer)。
[*]将 Moduliths 依赖项添加到您的项目中:<dependencies>
<!-- For the @Modulith annotation --> <dependency> <groupId>org.moduliths</groupId> <artifactId>moduliths-core</artifactId> <version>$modulith.version</version> </dependency> <!-- Test support --> <dependency> <groupId>org.moduliths</groupId> <artifactId>moduliths-test</artifactId> <version>$modulith.version</version> <scope>test</scope> </dependency> </dependencies> <!-- If you use snapshots --> <repositories> <repository> <id>spring-snapshots</id> <url>https:<font><i>//repo.spring.io/libs-snapshot</url></i></font><font> </repository> </repositories> </font>
Spring Boot(十八):使用Spring Boot集成FastDFS
Spring Boot(十八):使用Spring Boot集成FastDFS
环境:Spring Boot最新版本1.5.9、jdk使用1.8、tomcat8.0
功能:使用Spring Boot将文件上传到分布式文件系统FastDFS中。
一、pom包配置
<dependency> <groupId>org.csource</groupId> <artifactId>fastdfs-client-java</artifactId> <version>1.27-SNAPSHOT</version> </dependency>
加入了fastdfs-client-java
包,用来调用FastDFS相关的API。
二、配置文件
resources目录下添加fdfs_client.conf
文件:
connect_timeout = 60 network_timeout = 60 charset = UTF-8 http.tracker_http_port = 8080 http.anti_steal_token = no http.secret_key = 123456 tracker_server = 192.168.53.85:22122 tracker_server = 192.168.53.86:22122
配置文件设置了连接的超时时间,编码格式以及tracker_server地址等信息。
三、封装FastDFS上传工具类
(1)封装FastDFSFile,文件基础信息包括文件名、内容、文件类型、作者等。
public class FastDFSFile { private String name; private byte[] content; private String ext; private String md5; private String author; //省略getter、setter
封装FastDFSClient类,包含常用的上传、下载、删除等方法。
(2)首先在类加载的时候读取相应的配置信息,并进行初始化。
static { try { String filePath = new ClassPathResource("fdfs_client.conf").getFile().getAbsolutePath();; ClientGlobal.init(filePath); trackerClient = new TrackerClient(); trackerServer = trackerClient.getConnection(); storageServer = trackerClient.getStoreStorage(trackerServer); } catch (Exception e) { logger.error("FastDFS Client Init Fail!",e); } }
(3)文件上传:
public static String[] upload(FastDFSFile file) { logger.info("File Name: " + file.getName() + "File Length:" + file.getContent().length); NameValuePair[] meta_list = new NameValuePair[1]; meta_list[0] = new NameValuePair("author", file.getAuthor()); long startTime = System.currentTimeMillis(); String[] uploadResults = null; try { storageClient = new StorageClient(trackerServer, storageServer); uploadResults = storageClient.upload_file(file.getContent(), file.getExt(), meta_list); } catch (IOException e) { logger.error("IO Exception when uploadind the file:" + file.getName(), e); } catch (Exception e) { logger.error("Non IO Exception when uploadind the file:" + file.getName(), e); } logger.info("upload_file time used:" + (System.currentTimeMillis() - startTime) + " ms"); if (uploadResults == null) { logger.error("upload file fail, error code:" + storageClient.getErrorCode()); } String groupName = uploadResults[0]; String remoteFileName = uploadResults[1]; logger.info("upload file successfully!!!" + "group_name:" + groupName + ", remoteFileName:" + " " + remoteFileName); return uploadResults; }
使用FastDFS提供的客户端storageClient来进行文件上传,最后将上传结果返回。
根据groupName和文件名获取文件信息。
public static FileInfo getFile(String groupName, String remoteFileName) { try { storageClient = new StorageClient(trackerServer, storageServer); return storageClient.get_file_info(groupName, remoteFileName); } catch (IOException e) { logger.error("IO Exception: Get File from Fast DFS failed", e); } catch (Exception e) { logger.error("Non IO Exception: Get File from Fast DFS failed", e); } return null; }
(4)下载文件:
public static InputStream downFile(String groupName, String remoteFileName) { try { storageClient = new StorageClient(trackerServer, storageServer); byte[] fileByte = storageClient.download_file(groupName, remoteFileName); InputStream ins = new ByteArrayInputStream(fileByte); return ins; } catch (IOException e) { logger.error("IO Exception: Get File from Fast DFS failed", e); } catch (Exception e) { logger.error("Non IO Exception: Get File from Fast DFS failed", e); } return null; }
(5)删除文件:
public static void deleteFile(String groupName, String remoteFileName) throws Exception { storageClient = new StorageClient(trackerServer, storageServer); int i = storageClient.delete_file(groupName, remoteFileName); logger.info("delete file successfully!!!" + i); }
使用FastDFS时,直接调用FastDFSClient对应的方法即可。
四、编写上传控制类
从MultipartFile中读取文件信息,然后使用FastDFSClient将文件上传到FastDFS集群中。
public String saveFile(MultipartFile multipartFile) throws IOException { String[] fileAbsolutePath={}; String fileName=multipartFile.getOriginalFilename(); String ext = fileName.substring(fileName.lastIndexOf(".") + 1); byte[] file_buff = null; InputStream inputStream=multipartFile.getInputStream(); if(inputStream!=null){ int len1 = inputStream.available(); file_buff = new byte[len1]; inputStream.read(file_buff); } inputStream.close(); FastDFSFile file = new FastDFSFile(fileName, file_buff, ext); try { fileAbsolutePath = FastDFSClient.upload(file); //upload to fastdfs } catch (Exception e) { logger.error("upload file Exception!",e); } if (fileAbsolutePath==null) { logger.error("upload file failed,please upload again!"); } String path=FastDFSClient.getTrackerUrl()+fileAbsolutePath[0]+ "/"+fileAbsolutePath[1]; return path; }
请求控制,调用上面方法saveFile()
。
@PostMapping("/upload") //new annotation since 4.3 public String singleFileUpload(@RequestParam("file") MultipartFile file, RedirectAttributes redirectAttributes) { if (file.isEmpty()) { redirectAttributes.addFlashAttribute("message", "Please select a file to upload"); return "redirect:uploadStatus"; } try { // Get the file and save it somewhere String path=saveFile(file); redirectAttributes.addFlashAttribute("message", "You successfully uploaded \'" + file.getOriginalFilename() + "\'"); redirectAttributes.addFlashAttribute("path", "file path url \'" + path + "\'"); } catch (Exception e) { logger.error("upload file failed",e); } return "redirect:/uploadStatus"; }
上传成功之后,将文件的路径展示到页面,效果图如下:
在浏览器中访问此Url,可以看到成功通过FastDFS展示:
以上是关于moduliths: 使用Spring Boot构建模块化单体架构的工具的主要内容,如果未能解决你的问题,请参考以下文章
spring boot:用shardingjdbc实现多数据源的分库分表(shardingsphere 4.1.1/spring boot 2.3.1)
Spring-boot2X基于sharding-jdbc3X分表分库