根据json构建uml类图代码工具实现

【摘要】本文介绍UML基本概念及相关类型作用,分析UML在研发场景下存在痛点, 分析改进思路,并阐述实现结构化json绘制uml类图的技术方案。最后总结下根据新技术方案,可进行的衍生拓展设计。

【关键词】: UML;JSON;建模

1.UML简介

1.1 什么是UML建模

UML(Unified Modeling Language,统一建模语言)是一种标准化的建模语言,用于在软件工程中可视化、描述、构建和记录软件系统的各种构件。UML图是用UML语言绘制的图表,用于表示系统的静态结构和动态行为。

下面主要列举一些常用的uml图如下

类图 (Class Diagram)

作用:表示系统中的类及其相互关系,描述系统的静态结构。
元素:类、接口、属性、方法、关联关系、继承关系、实现关系等。

用例图 (Use Case Diagram)

作用:展示系统的功能及其用户(称为参与者),描述系统的功能需求。
元素:用例、参与者、系统边界、关联关系等。

序列图 (Sequence Diagram)

作用:显示对象之间的交互及其调用顺序,描述系统的动态行为。
元素:对象、生命线、消息、激活等。

活动图 (Activity Diagram)

作用:表示系统中活动的流程,类似于流程图,用于描述业务流程或方法的逻辑。
元素:活动、决策节点、并行节点、开始和结束节点等。

状态图 (State Diagram)

作用:表示对象在生命周期中的状态变化及其触发事件。
元素:状态、转换、事件、起始状态、终止状态等。

组件图 (Component Diagram)

作用:展示系统的物理结构,表示系统中的软件组件及其关系。
元素:组件、接口、依赖关系等。

部署图 (Deployment Diagram)

作用:表示系统的硬件配置及其部署的组件。
元素:节点、组件、关联等。

对象图 (Object Diagram)

作用:显示系统在某个时刻的对象实例及其关系,是类图的实例化。
元素:对象、链接等。

1.2 使用UML建模的好处

系统设计和分析

  • 帮助系统设计人员和开发人员理解和设计系统的结构和行为。
  • 在系统开发的早期阶段,用于捕捉和分析需求。

文档记录

  • 提供系统的图形化文档,有助于维护和升级系统。
  • 作为开发过程中的参考资料。

沟通和交流

  • 帮助团队成员之间更好地沟通和交流系统的设计和需求。
  • 为项目利益相关者提供直观的系统视图。

代码生成和逆向工程

  • 有助于从模型生成代码或从代码生成模型,支持自动化开发工具。

质量保证和测试

  • 通过图形化的模型,可以更容易地发现系统设计中的问题和缺陷。
  • 有助于测试用例的设计和验证。

2.当前UML在研发场景下痛点

uml工具虽然好,但是在我们实际工作工作中,会发现大部分软件开发都没有使用起来。
原因笔者分析如下:

  1. 研发觉得开发前规范设计是额外的工作量,没有直接写代码来的快, 特别在项目周期短,甚至产品生命都不长的研发工作中,显得特别没有性价比。
  2. UML设计侧重点更多是设计沟通工具,UML建模和实际的代码之间关系,是比较弱的逻辑关系,而且需要人为手动维护。可能设计之初,UML建模和代码是一致的。但是随着产品迭代,UML忘记及时维护,就会UML版本落后于真实的代码。一个不准确的设计,对于理解系统可能会误导。这样反过来就导致更不愿意使用UML。

那么该如何解决呢?思路如下

  1. 互联网下班场更多的存量系统之间的竞争,系统运行时间长,业务复杂,拥有良好设计文档工具的系统更够降低维护成本,快速应对新需求。
  2. 从上面UML建模优点可以分析,UML实际上是可以作为代码生成的模板,也就是不仅仅是设计工具,也是代码的生产工具。笔者人为只有把uml和生产结合起来,才能保证保留UML建模优点,同时准确反映最新代码的设计。

有了解决思路下面围绕“代码即设计”和“设计及代码”的思路,来改变UML使用方式。

3.UML工具的优化实现

研发过程中绘制UML,主要有两种方式使用,在线UML建模网站的图形界面快速绘制和基于文本的图表绘制工具(mermaid/plantUML)

方式一: 以UML中的类图为例,在语雀的在线uml绘制类图
在这里插入图片描述
这种方式优点是方便快捷,可以最快方式绘图。确定依赖第三方,纯设计工具无法解析内容,实现拓展功能。

方式二:mermaid示例

classDiagram
    Animal <|-- Duck
    Animal <|-- Fish
    Animal <|-- Zebra
    Animal : +int age
    Animal : +String gender
    Animal: +isMammal()
    Animal: +mate()
    class Duck{
        +String beakColor
        +swim()
        +quack()
    }
    class Fish{
        -int sizeInFeet
        -canEat()
    }
    class Zebra{
        +bool is_wild
        +run()
    }

并通过解析器将这些文本渲染成图形效果:

Animal
+int age
+String gender
+isMammal()
+mate()
Duck
+String beakColor
+swim()
+quack()
Fish
-int sizeInFeet
-canEat()
Zebra
+bool is_wild
+run()

这种方式优点是可控性强,使用纯文本记录,和git管理工具结合可以版本化。缺点是需要手写,非技术人员上手难度大,而且符号很容易写错,导致图片渲染失败。

因此需要以下改进

  1. 非结构化的写法使用结构化,如使用json替代。
  2. json数据通过解析器自动翻译成mermaid的代码
  3. 支持在线实时预览

3.1 json结构设计

uml 分解为节点与边,分别代表类与类之间的关系。节点内部包含属性和方法,对应java的字段及成员方法

- 节点(数组)
	- 属性
	- 方法 
- 关系(数组)

json 示例

{
  "classDiagram": {
    "nodes": [
      {
        "id": "Order",
        "type": "AggregateRoot",
        "properties": [
          { "name": "orderId", "type": "String", "description": "订单 ID", "pk": true },
          { "name": "orderItems", "type": "List[OrderItem]", "description": "订单项列表" },
          { "name": "accountId", "type": "String", "description": "账户 ID" },
          { "name": "address", "type": "Address", "description": "地址对象" },
          { "name": "status", "type": "Integer", "description": "订单状态" },
          { "name": "createTime", "type": "Date", "description": "创建时间" },
          { "name": "updateTime", "type": "Date", "description": "更新时间" },
          { "name": "paymentTime", "type": "Date", "description": "支付时间" },
          { "name": "amount", "type": "BigDecimal", "description": "订单总金额" },
          { "name": "currency", "type": "String", "description": "货币类型", "defaultValue": "CNY" }
        ],
        "methods": [
          { "name": "addItem", "parameters": [{ "name": "product", "type": "Product" }, { "name": "quantity", "type": "int" }], "description": "添加商品到订单" },
          { "name": "removeItem", "parameters": [{ "name": "productId", "type": "String" }], "description": "移除商品" },
          { "name": "clearItems", "description": "清空订单项" },
          { "name": "calculateTotalPrice", "returnType": "BigDecimal", "description": "计算订单总金额" },
          { "name": "calculateGifts", "description": "计算赠品" },
          { "name": "verifyCloseStatus", "returnType": "Order", "description": "验证关闭状态" },
          { "name": "verifyOwner", "parameters": [{ "name": "accountId", "type": "String" }], "returnType": "Order", "description": "验证所有者" }
        ]
      },
      {
        "id": "OrderItem",
        "type": "Entity",
        "properties": [
          { "name": "itemId", "type": "String", "description": "订单项ID" , "pk": true},
          { "name": "product", "type": "ProductSnapShot", "description": "商品对象" },
          { "name": "quantity", "type": "int", "description": "数量" },
          { "name": "price", "type": "Price", "description": "价格对象" },
          { "name": "remarks", "type": "String", "description": "备注" }
        ]
      },
    
    ],
    "relationships": [
      { "source": "Order", "target": "OrderItem", "type": "contains", "description": "包含" }
    ]
  }
}


3.2 json类图解析器实现

public class MermaidClassDiagramGenerator {

    private static final Logger LOGGER = LoggerFactory.getLogger(MermaidClassDiagramGenerator.class);

    public static void main(String[] args) throws IOException {

        
        // 或者直接提交 JSON 内容进行解析
        String jsonContent = "{\n" +
                "  \"classDiagram\": {\n" +
                "    \"nodes\": [\n" +
                "      {\n" +
                "        \"id\": \"Order\",\n" +
                "        \"type\": \"AggregateRoot\",\n" +
                "        \"properties\": [\n" +
                "          {\n" +
                "            \"name\": \"orderId\",\n" +
                "            \"type\": \"String\",\n" +
                "            \"description\": \"订单 ID\",\n" +
                "            \"pk\": true\n" +
                "          }\n" +
                "        ],\n" +
                "        \"methods\": [],\n" +
                "        \"relationships\": []\n" +
                "      }\n" +
                "    ]\n" +
                "  }\n" +
                "}";
        String markdownFromContent = generateMermaidMarkdownFromContent(jsonContent);
        System.out.println(markdownFromContent);
    }

    public static String generateMermaidMarkdownFromFile(String jsonFilePath) throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        JsonNode rootNode = objectMapper.readTree(new File(jsonFilePath));
        return generateMermaidMarkdown(rootNode);
    }

    public static String generateMermaidMarkdownFromContent(String jsonContent) throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        JsonNode rootNode = objectMapper.readTree(jsonContent);
        return generateMermaidMarkdown(rootNode);
    }

    private static String generateMermaidMarkdown(JsonNode rootNode) {
        StringBuilder markdownBuilder = new StringBuilder();
//        markdownBuilder.append("<script class='mermaid'>\n");
        markdownBuilder.append("classDiagram\n\n");

        // Process nodes
        Iterator<JsonNode> nodesIterator = rootNode.path("classDiagram").path("nodes").elements();
        while (nodesIterator.hasNext()) {
            JsonNode node = nodesIterator.next();
            String id = node.path("id").asText();
            String type = node.path("type").asText();
            LOGGER.info("解析ID[{}] 类型[{}]", id, type);
            String classText = "class " + id + " {\n";
            if (!type.isEmpty()) {
                classText += "\t<< " + type + " >>\n";
            }
            markdownBuilder.append(classText);

            // Process properties
            Iterator<JsonNode> propertiesIterator = node.path("properties").elements();
            while (propertiesIterator.hasNext()) {
                JsonNode property = propertiesIterator.next();
                String propertyName = property.path("name").asText();
                String propertyType = property.path("type").asText();
                String propertyDescription = property.path("description").asText();
                String propertyExtra = property.path("pk").asBoolean() ? "[pk]" : "";
                String propertyText = "\t- " + propertyName + ": " + propertyType + " [" + propertyDescription + "]" + propertyExtra + "\n";

                markdownBuilder.append(propertyText);
                // 引用关系推断
                buildEntityRelationShip(propertyType, node, rootNode);

            }

            // Process methods
            Iterator<JsonNode> methodsIterator = node.path("methods").elements();
            while (methodsIterator.hasNext()) {
                JsonNode method = methodsIterator.next();
                String methodName = method.path("name").asText();
                String methodReturnType = method.path("returnType").asText();
                String methodDescription = method.path("description").asText();
                StringBuilder parametersBuilder = new StringBuilder();
                Iterator<JsonNode> parametersIterator = method.path("parameters").elements();
                while (parametersIterator.hasNext()) {
                    JsonNode parameter = parametersIterator.next();
                    String parameterName = parameter.path("name").asText();
                    String parameterType = parameter.path("type").asText();
                    parametersBuilder.append(parameterName).append(": ").append(parameterType).append(", ");
                }
                if (parametersBuilder.length() > 0) {
                    parametersBuilder.setLength(parametersBuilder.length() - 2); // Remove the trailing comma and space
                }
                String methodText = "\t+ ";
                if (!methodReturnType.isEmpty()) {
                    methodText += methodReturnType + " ";
                } else {
                    methodText += "void ";
                }
                methodText += methodName + "(" + parametersBuilder.toString() + ") : " + methodDescription + "\n";
                markdownBuilder.append(methodText);
            }

            markdownBuilder.append("}\n\n");
        }

        // Process relationships
        Iterator<JsonNode> relationshipsIterator = rootNode.path("classDiagram").path("relationships").elements();
        while (relationshipsIterator.hasNext()) {
            JsonNode relationship = relationshipsIterator.next();
            String source = relationship.path("source").asText();
            String target = relationship.path("target").asText();
            String type = relationship.path("type").asText();
            String description = relationship.path("description").asText();
            String relationshipText = "";
            if ("contains".equals(type) || "has".equals(type)) {
                relationshipText = source + " *-- " + target + " : " + type + "\n";
            } else if ("use".equals(type)) {
                relationshipText = source + " ..> " + target + " : " + type + "\n";
            }
            markdownBuilder.append(relationshipText);
        }

//        markdownBuilder.append("</script>\n");
        System.out.println(markdownBuilder);
        return markdownBuilder.toString();
    }

    private static void buildEntityRelationShip(String propertyType, JsonNode currentNode, JsonNode rootNode) {
        ObjectMapper objectMapper = new ObjectMapper();
        ObjectNode classDiagramNode = (ObjectNode) rootNode.path("classDiagram");
        ArrayNode relationshipsNode;

        if (classDiagramNode.has("relationships")) {
            relationshipsNode = (ArrayNode) classDiagramNode.path("relationships");
        } else {
            relationshipsNode = objectMapper.createArrayNode();
            classDiagramNode.set("relationships", relationshipsNode);
        }

        Iterator<JsonNode> nodesIterator = rootNode.path("classDiagram").path("nodes").elements();
        String realType = propertyType;
        if (propertyType.endsWith("[]")) { // 解析数组类型如 Date[]
            realType = propertyType.substring(0, propertyType.length() - 2);
        } else if (propertyType.startsWith("List[")) {
            realType = propertyType.substring(5, propertyType.length() - 1);
        }

        while (nodesIterator.hasNext()) {
            JsonNode node = nodesIterator.next();
            String id = node.path("id").asText();
            if (realType.equals(id)) {
                String source = currentNode.path("id").asText();
                String target = node.path("id").asText();


                // Check if the relationship already exists
                boolean relationshipExists = false;
                Iterator<JsonNode> existingRelationships = relationshipsNode.elements();
                while (existingRelationships.hasNext()) {
                    JsonNode existingRelationship = existingRelationships.next();
                    if (existingRelationship.path("source").asText().equals(source) &&
                            existingRelationship.path("target").asText().equals(target)) {
                        relationshipExists = true;
                        break;
                    }
                }

                // If relationship does not exist, add it
                if (!relationshipExists) {
                    ObjectNode relationshipNode = objectMapper.createObjectNode();
                    relationshipNode.put("source", source);
                    relationshipNode.put("target", target);
                    relationshipNode.put("type", "has");
                    relationshipsNode.add(relationshipNode);
                }
            }
        }
    }
}


3.3 在线uml类图渲染实现

3.3.1 前端渲染页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/mermaid@10.7.0/dist/mermaid.min.js"></script>
</head>
<body>

<div>
    <label for="diagram-input">Diagram Type:</label>
    <input type="text" id="diagram-input" value="class_diagram">
    <button id="button_1" type="button" onclick="fetchAndRender()">实时渲染</button>
</div>
<div id="mermaid-container">


</div>
</body>

<script  type="text/javascript">

    function fetchAndRender() {
        var rootValue = $('#diagram-input').val();
        $.ajax({
            url: 'mermaid/classDiagram', // Replace 'your_backend_url' with your actual backend URL
            data: {root: rootValue},
            method: 'GET',
            success: function(response) {
                renderMermaid(response.data);
            },
            error: function(xhr, status, error) {
                console.error('Error fetching data:', error);
            }
        });
    }

    function renderMermaid(graphDefinition) {
        console.log(graphDefinition)
        $('#mermaid-container').html(graphDefinition).removeAttr('data-processed');
        mermaid.init(undefined, $("#mermaid-container"));
    }

    // Call fetchAndRender() once when the page loads to initially render the graph
    $(document).ready(function() {
        // fetchAndRender();
    });
</script>
</html>

3.3.2 后端数据接口

package com.sample.controller;

import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;
import java.io.InputStream;
import java.text.MessageFormat;
import java.util.Map;

@RestController
public class MermaidController {

    @GetMapping("/mermaid/classDiagram")
    public Map<String, Object> getClassDiagram(String root) throws IOException {
        Resource data = new ClassPathResource(MessageFormat.format("data/{0}.json", root));
        if (!data.exists()) {
            data = new ClassPathResource("data/class_diagram_v1.json");
        }

        try (InputStream inputStream = data.getInputStream()) {
            String json = new String(FileCopyUtils.copyToByteArray(inputStream));
            // 根据json生成mermaid的语法构建uml类图
            String classDiagram = MermaidClassDiagramGenerator.generateMermaidMarkdownFromContent(json);
            return Map.of("data",  classDiagram);
        }
    }

}

3.4 在线渲染效果

在这里插入图片描述
完整json示例

{
  "classDiagram": {
    "nodes": [
      {
        "id": "Order",
        "type": "AggregateRoot",
        "properties": [
          { "name": "orderId", "type": "String", "description": "订单 ID", "pk": true },
          { "name": "orderItems", "type": "List[OrderItem]", "description": "订单项列表" },
          { "name": "accountId", "type": "String", "description": "账户 ID" },
          { "name": "address", "type": "Address", "description": "地址对象" },
          { "name": "status", "type": "Integer", "description": "订单状态" },
          { "name": "createTime", "type": "Date", "description": "创建时间" },
          { "name": "updateTime", "type": "Date", "description": "更新时间" },
          { "name": "paymentTime", "type": "Date", "description": "支付时间" },
          { "name": "amount", "type": "BigDecimal", "description": "订单总金额" },
          { "name": "currency", "type": "String", "description": "货币类型", "defaultValue": "CNY" }
        ],
        "methods": [
          { "name": "addItem", "parameters": [{ "name": "product", "type": "Product" }, { "name": "quantity", "type": "int" }], "description": "添加商品到订单" },
          { "name": "removeItem", "parameters": [{ "name": "productId", "type": "String" }], "description": "移除商品" },
          { "name": "clearItems", "description": "清空订单项" },
          { "name": "calculateTotalPrice", "returnType": "BigDecimal", "description": "计算订单总金额" },
          { "name": "calculateGifts", "description": "计算赠品" },
          { "name": "verifyCloseStatus", "returnType": "Order", "description": "验证关闭状态" },
          { "name": "verifyOwner", "parameters": [{ "name": "accountId", "type": "String" }], "returnType": "Order", "description": "验证所有者" }
        ]
      },
      {
        "id": "OrderItem",
        "type": "Entity",
        "properties": [
          { "name": "itemId", "type": "String", "description": "订单项ID" , "pk": true},
          { "name": "product", "type": "ProductSnapShot", "description": "商品对象" },
          { "name": "quantity", "type": "int", "description": "数量" },
          { "name": "price", "type": "Price", "description": "价格对象" },
          { "name": "remarks", "type": "String", "description": "备注" }
        ]
      },
      {
        "id": "Address",
        "type": "ValueObject",
        "properties": [
          { "name": "street", "type": "String", "description": "街道" },
          { "name": "city", "type": "String", "description": "城市" },
          { "name": "state", "type": "String", "description": "省份" },
          { "name": "zipCode", "type": "String", "description": "邮政编码" }
        ]
      },
      {
        "id": "Price",
        "type": "ValueObject",
        "properties": [
          { "name": "amount", "type": "BigDecimal", "description": "金额" },
          { "name": "currency", "type": "String", "description": "货币类型" }
        ]
      },
      {
        "id": "ProductSnapShot",
        "type": "ValueObject",
        "properties": [
          { "name": "id", "type": "Long", "description": "商品 ID" },
          { "name": "code", "type": "String", "description": "商品编码" },
          { "name": "name", "type": "String", "description": "商品名称" },
          { "name": "price", "type": "Price", "description": "商品价格" }
        ]
      },
      {
        "id": "Product",
        "type": "Entity",
        "properties": [
          { "name": "id", "type": "Long", "description": "商品 ID" },
          { "name": "code", "type": "String", "description": "商品编码" },
          { "name": "productName", "type": "String", "description": "商品名称" },
          { "name": "price", "type": "Price", "description": "商品价格" }
        ]
      },
      {
        "id": "OrderDomainService",
        "type": "DomainService",
        "methods": [
          { "name": "addItem", "parameters": [{ "name": "product", "type": "Product" }, { "name": "quantity", "type": "int" }], "description": "添加商品到订单" },
          { "name": "removeItem", "parameters": [{ "name": "productId", "type": "String" }], "description": "移除商品" },
          { "name": "clearItems", "description": "清空订单项" },
          { "name": "verifyCloseStatus", "returnType": "Order", "description": "验证关闭状态" },
          { "name": "verifyOwner", "parameters": [{ "name": "accountId", "type": "String" }], "returnType": "Order", "description": "验证所有者" }
        ]
      },
      {
        "id": "OrderAppService",
        "type": "AppService",
        "methods": [
          { "name": "bpmnInvoke", "description": "流程编排" }
        ]
      },
      {
        "id": "OrderInterfaceService",
        "type": "Service"
      },
      {
        "id": "OrderPageQuery",
        "type": "Query",
        "properties": [
          { "name": "productName", "type": "String", "description": "商品名称"},
          { "name": "createTime", "type": "Date", "description": "订单创建时间" }
        ],
        "methods": [
          { "name": "invoke", "description": "执行查询", "returnType": "List[OrderPageQueryDTO]"}
        ]
      },
      {
        "id": "OrderDetailQuery",
        "type": "Query",
        "properties": [
          { "name": "orderId", "type": "String", "description": "订单 ID"}
        ]
      },
      {
        "id": "OrderPageQueryDTO",
        "type": "DTO",
        "properties": [
          { "name": "orderId", "type": "String", "description": "订单 ID" },
          { "name": "accountId", "type": "String", "description": "账户 ID" },
          { "name": "address", "type": "Address", "description": "地址对象" },
          { "name": "status", "type": "Integer", "description": "订单状态" },
          { "name": "createTime", "type": "Date", "description": "创建时间" },
          { "name": "updateTime", "type": "Date", "description": "更新时间" },
          { "name": "paymentTime", "type": "Date", "description": "支付时间" },
          { "name": "amount", "type": "BigDecimal", "description": "订单总金额" },
          { "name": "currency", "type": "String", "description": "货币类型", "defaultValue": "CNY" }
        ]
      }

    ]
  }
}

4. 总结

本文分析UML作用及使用现状,并且通过json结构方式实现的UML模型中类图构建,降低了文本解析的绘图成本。同时后续可以根据json结构化信息,已经包含了类描述信息,可以从两个方向进行进一步伸延设计。

  • 根据类图定义注册接口
  • 根据类定义自动生成数据库定义

通过这样思路可以将UML建模和后续工作结合起来,设计工具变成代码生产的工具,或者设计出低代码工具。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值