前篇介绍了如何使用PlantUML代码生成UML图 --《Diagrams as Code -- PlantUML, 不再“画”图》,还介绍了关于C4模型 --《可视化软件系统架构 -- C4模型》。那么能不能用PlantUML生成C4架构图呢?当然可以,解决方案是C4-PlantUML。
C4-PlantUML 结合了PlantUML和C4 模型的优势,通过使用开源和独立于平台的工具的直观语言,提供了一种描述和传达软件架构的简单方法(尤其是在前期设计阶段)。接下来,我们来看看使用C4-PlantUML的案例。
系统上下文图
上图是虚构的阿里无人机科技公司的B2B销售系统,经销商可以在销售系统上购买商品,内部销售人员将会审核订单,销售运营人员可以在销售系统上查看销售报表。
c4-plantuml使用Person定义系统用户:
Person(salesUser, "内部销售人员", "审核订单")
Person(operationUser, "销售运营人员", "查看销售报表")
使用Person_Ext定义外部用户经销商:
Person_Ext(agentUser, "经销商", "购买商品")
使用System定义系统:
System(salesSystem, "销售系统", "线上销售无人机商品")
使用System_Ext定义外部系统:
System_Ext(ERP, "ERP", "记账")
使用Rel表示关系,例如,内部销售人员使用销售系统:
Rel(salesUser, salesSystem, "Uses")
本例完整的c4-plantuml代码如下:
@startuml
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml
LAYOUT_WITH_LEGEND()
Person_Ext(agentUser, "经销商", "购买商品")
Enterprise_Boundary(b0, "阿里无人机科技") {
Person(salesUser, "内部销售人员", "审核订单")
Person(operationUser, "销售运营人员", "查看销售报表")
System(salesSystem, "销售系统", "线上销售无人机商品")
System_Ext(ERP, "ERP", "记账")
System_Ext(TGP, "TGP", "开票")
System_Ext(MDM, "MDM", "主数据")
}
Rel(agentUser, salesSystem, "Uses")
Rel(salesUser, salesSystem, "Uses")
Rel(operationUser, salesSystem, "Uses")
Rel(salesSystem, MDM, "查询基础数据", "HTTP")
Rel(salesSystem, ERP, "记账", "HTTP")
Rel(salesSystem, TGP, "开票", "MQ")
@enduml
容器图
上图放大了销售系统,显示其中的应用程序、数据存储和消息队列等中间件。系统是前后端分离的微服务架构,由Web前端、API网关、订单服务、结算服务、基础服务几个应用组成,每个后端微服务有各自独立使用的数据库,订单服务和结算服务的集成使用消息队列的方式。
c4-plantuml使用关键字Container定义应用:
Container(orderService, "订单服务", "java", "提供下单、审核和查看订单服务")
使用关键字ContainerDb定义数据库:
ContainerDb(orderDatabase, "订单数据库", "MySQL", "存储订单数据")
使用关键字ContainerQueue定义消息队列:
ContainerQueue(rocketMQ, "消息队列", "RocketMQ")
使用Rel表示关系,例如读写数据库:
Rel(orderService, orderDatabase, "Reads/Writes")
本例完整的c4-plantuml代码如下:
@startuml
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml
!define DEVICONS https://raw.githubusercontent.com/tupadr3/plantuml-icon-font-sprites/master/devicons
!define DEVICONS2 https://raw.githubusercontent.com/tupadr3/plantuml-icon-font-sprites/master/devicons2
!define FONTAWESOME https://raw.githubusercontent.com/tupadr3/plantuml-icon-font-sprites/master/font-awesome-5
!include DEVICONS/java.puml
!include DEVICONS/mysql.puml
!include FONTAWESOME/users.puml
!include FONTAWESOME/rocket.puml
!include DEVICONS2/redis.puml
!include DEVICONS2/vuejs.puml
LAYOUT_WITH_LEGEND()
Person_Ext(agentUser, "经销商", "购买商品")
Person(salesUser, "内部销售人员", "审核订单")
Person(operationUser, "销售运营人员", "查看销售报表")
System_Boundary(c1, "销售系统") {
Container(web, "Web", "vue", "用户通过浏览器访问网站", $sprite="vuejs")
Container(apiGateway, "API网关", "java", "限流、鉴权、路由", $sprite="java")
AddBoundaryTag("v1", $bgColor="#BADFC9", $fontColor="black", $borderColor="#BADFC9")
Container(orderService, "订单服务", "java", "提供下单、审核和查看订单服务", $sprite="java", $tags="microservice")
Container(financeService, "结算服务", "java", "提供费用结算服务", $sprite="java")
Container(baseService, "基础服务", "java", "提供基础服务", $sprite="java")
ContainerDb(orderDatabase, "订单数据库", "MySQL", "存储订单数据", $sprite="mysql")
ContainerDb(financeDatabase, "结算数据库", "MySQL", "存储结算单数据", $sprite="mysql")
ContainerDb(baseDatabase, "基础数据库", "MySQL", "存储基础数据", $sprite="mysql")
ContainerQueue(rocketMQ, "消息队列", "RocketMQ", $sprite="rocket")
}
Rel(agentUser, web, "Uses")
Rel(salesUser, web, "Uses")
Rel(operationUser, web, "Uses")
Rel(web, apiGateway, "Uses", "https")
Rel(apiGateway, orderService, "Uses")
Rel(apiGateway, financeService, "Uses")
Rel(apiGateway, baseService, "Uses")
Rel(orderService, orderDatabase, "Reads/Writes")
Rel(financeService, financeDatabase, "Reads/Writes")
Rel(baseService, baseDatabase, "Reads/Writes")
Rel(orderService, rocketMQ, "Reads/Writes")
Rel(financeService, rocketMQ, "Reads/Writes")
@enduml
组件图
上图放大了订单服务,展示订单服务的内部组件。RestController暴露API端点,提供HTTP访问服务,ApplicationService提供订单应用服务,DomainModel定义了领域模型处理业务逻辑,Repository组件增删改查数据库,ServiceProxy集成其他服务或者外部系统。
c4-plantuml使用关键字Component定义组件:
Component(controller, "Rest Controller", "java, spring", "暴露API端点,提供HTTP访问服务")
使用Rel表示关系,例如RestController调用ApplicationService:
Rel(controller, applicationService, "Invokes")
本例完整的c4-plantuml代码如下:
@startuml
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Component.puml
Container(financeService, "结算服务", "java", "结算服务", $sprite="java")
Container(baseService, "基础服务", "java", "基础服务", $sprite="java")
ContainerDb(orderDatabase, "订单数据库", "MySQL", "订单数据", $sprite="mysql")
ContainerQueue(rocketMQ, "消息队列", "RocketMQ", $sprite="rocket")
Container_Boundary(orderService, "订单服务") {
Component(controller, "Rest Controller", "java, spring", "暴露API端点,提供HTTP访问服务")
Component(applicationService, "Application Service", "java", "订单应用服务")
Component(domainModel, "Domain Model", "java", "订单领域模型")
Component(repository, "Repository", "jpa", "增删改查")
Component(serviceProxy, "Service Proxy", "java", "集成其他服务或者外部系统")
Rel(controller, applicationService, "Invokes")
Rel(applicationService, domainModel, "Invokes")
Rel(applicationService, repository, "Invokes")
Rel(applicationService, serviceProxy, "Invokes")
}
Rel(serviceProxy, baseService, "GET")
Rel_L(serviceProxy, rocketMQ, "Send Message")
Rel(financeService, rocketMQ, "Receive Message")
Rel(repository, orderDatabase, "Reads/Writes")
@enduml
代码图
上图放大了domain-model组件,用UML类图展示实现类模型。
c4-plantuml的代码图使用plantuml类图语法,可阅读前篇《Diagrams as Code -- PlantUML, 不再“画”图》。
本例完整的c4-plantuml代码如下:
@startuml
package "domain model" {
Order *-> "*" LineItem
LineItem --> Product
Order --> PaymentType
PaymentType ^-- WechatPay
PaymentType ^-- AliPay
Order --> Customer
}
@enduml
如何渲染出图?
c4-plantuml扩展了plantuml,所以渲染方式同plantuml是一样的,主要以下几种方式:
- 在线网站,如https://plantumleditor.com/webedit/
- 部署在本地、服务器或者云
- Visual Studio Code,安装plantuml插件
- Jetbrains系列产品,也支持安装plantuml插件,如Idea, Rider, WebStorm等等
以上几种都可以即时在屏幕上渲染出图片,也支持导出图片。如果你发现排版上需要调整,可以尝试使用以下布局方式:
- LAYOUT_TOP_DOWN()
- LAYOUT_LEFT_RIGHT()
- LAYOUT_LANDSCAPE()
或者强制调整REL方向:
- REL_L(a, b): b在a左边
- REL_R(a, b): b在a右边
- REL_U(a, b): b在a上边
- REL_D(a, b): b在a下边