抽象思维实践——ddl2plantuml开发记录

项目源码地址: github

背景

利用plantuml绘制架构评审图时,发现数据库ER图手写字段信息成本太大,需要一个把DB表结构转换为plantuml格式的工具。
搜索了一番,没有发现支持工具,所以准备手撸一个,并记录下设计及编码实现的过程。

需求

一句话需求: 读取数据库表结构,转换为plantuml格式的ER图。

设计

根据需求抽象出下列概念

基础元素(一个输入一个输出)

  1. ER -> plantuml格式er
  2. db_schema -> 数据库结构,ddl语句是其中一种实现形式

扩展概念

  1. table -> 表结构信息
  2. template -> 模板,定义er图输出格式

操作行为

  1. reader -> 读取数据库结构,识别 table
  2. parser -> 根据 tabletemplate 转换为ER
  3. writer -> 输出ER图文件

整体交互

design

选型

都是基本的文件及String操作,通过 druid 进行sql解析

编码实现

Reader

读取ddl.sql文件,解析成 table

interface Reader {

    fun read(dbType: String? = DEFAULT_DB_TYPE): Iterable<Table>

    fun extract(dbType: String, sql: String): Table {
    ...    
    }
}

class FileReader(private val path: String) : Reader {

    override fun read(dbType: String?): Iterable<Table> {
        return Files.readAllLines(Paths.get(path))
                .filter { !it.startsWith("#") }
                .joinToString("")
                .split(";")
                .filter { it.isNotBlank() }
                .map { extract(dbType?: DEFAULT_DB_TYPE, it) }
                .toList()
    }

}

Writer

template通过resource文件管理,接收table输出plantuml格式ER

interface Writer {

    fun write(tables: Iterable<Table>)

    fun parse(tables: Iterable<Table>): String {
        val template = Thread.currentThread().contextClassLoader.getResource("dot.template")!!.readText()

        val content = tables.joinToString("") { table ->
            val columns = table.columnList.joinToString("\n") { "${it.notNullNameWrapper()} ${it.type} ${it.defaultValue} ${it.comment}" }
            "Table(${table.name}, \"${table.name}\\n(${table.comment})\"){ \n $columns + \n } \n"
        }

        return template.replace("__content__", content)
    }

    private fun Column.notNullNameWrapper(): String {
        return if (this.notNull) {
            "not_null(${this.name})"
        } else {
            this.name
        }
    }
}

class FileWriter(private val path: String) : Writer {

    override fun write(tables: Iterable<Table>) {
        Files.write(Paths.get(path), parse(tables).toByteArray())
    }

}

Main

fun main(args: Array<String>) {

    val inPath = args[0]
    val outPath = args[1]
    val dbType = args.getOrNull(2)

    FileReader(inPath).read(dbType)
            .apply { FileWriter(outPath).write(this) }

}

效果

java -jar ddl2plantuml.jar ddl.sql er.puml

程序会读取当前目录下的ddl.sql文件,并转换生成er.puml文件。

result

Welcome to my other publishing channels