├── .gitignore ├── LICENSE ├── README.md ├── accu.txt ├── build.sbt ├── code_template ├── java │ ├── FileOutputEndpointTemplate.java │ ├── OutputEndpoint.java │ └── PureFunction.java └── scala │ ├── AliyunHttpInputEndpoint.scala │ ├── AliyunOSSOutputEndpointTemplate.scala │ ├── CommandLineArgsInputEndpoint.scala │ ├── CommandLineInputEndpoint.scala │ ├── FileInputEndpointTemplate.scala │ └── StreamRequestHandlerTemplate.scala ├── deploy-aliyun.sh ├── genCode.sh ├── genCodeAliyun.sh ├── lib └── type-flow_2.13-0.0.1.jar ├── localoutput ├── accu.txt └── record.txt ├── project ├── build.properties └── plugins.sbt ├── ref_code ├── LoadAccumulateValue.java ├── LoadAccumulateValueHandler.scala ├── SaveAccumulateValue.java └── Split.java ├── src └── main │ └── scala │ └── com │ └── github │ └── notyy │ └── example │ └── HelloWorld.scala ├── type-flow-assembly-0.0.1.jar └── typeflow ├── README.md ├── newModel_v1.puml ├── newModel_v1_diff.puml ├── newModel_v1_record.puml ├── newModel_v1_replay.puml ├── newModel_v1_state.puml └── newModel_v1_state_aliyun.puml /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.log 3 | project/target/ 4 | .idea/ 5 | project/project/ 6 | target/ 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # scala_template_typeflow 2 | 3 | [TOC] 4 | 5 | 类型流是我设计的用于大规模软件工厂的**方法论**和开发工具,以云函数计算(Serverless)为目标平台、以领域模型驱动和函数式编程为指导。 6 | 7 | 当前还出于比较早期的阶段,暂时请勿用于生产,但非常欢迎开发人员尝试使用,给我反馈,让我更好的改进,给大家提供更好的开发工具。 8 | 9 | 这个给出一个Step by Step的例子供大家了解和体验类型流开发 10 | 11 | ### 环境准备 12 | 由于使用了一些Scala的语言特性,当前版本暂时只支持sbt开发Scala和Java混编的工程,以后会支持纯Java的工程。你需要安装好sbt构建工具,mac下执行`brew install sbt`即可。 13 | 其他环境的安装和使用请参考官网:https://www.scala-sbt.org/download.html 14 | 15 | sbt安装好后请在工程目录下执行`sbt update`,sbt将会下载所需的依赖,类似Maven和Gradle。 如有条件科学上网最好,否则此过程会耗时很长。 16 | 17 | 依赖更新完成后,将工程导入你的开发工具中(我使用的是intellij+scala插件)。 18 | 在IDE里执行src/main/scala目录下的com.github.notyy.example.HelloWorld,如果成功输出`hello,world`则说明开发环境准备成功。 19 | 20 | 当前版本使用plantuml来建模(新的html的前端界面已经在开发中),图形化显示plantuml的模型文件有两种方式: 21 | 1. 参考官方文档安装:http://plantuml.com/zh/starting ,注意还需要安装dot 22 | 2. 用[在线服务器](http://www.plantuml.com/plantuml/uml/SyfFKj2rKt3CoKnELR1Io4ZDoSa70000) 在表单里把plantuml的内容提交上去。 23 | 24 | ### 先从一个简单的例子开始 25 | 打开`typeflow/newModel_v1.puml`,你应该看到一个这样的模型图 26 | 27 | ![模型图](http://www.plantuml.com/plantuml/png/bP31QlCm48JF_pw5o3s7Fxc546ARbXhIrBUUXIKS4MbbfDr2A7ttsb6etPe4R30icc-6sOLz91c8dGM8PuEj3DA9sieOnIVyosLzRe9dL8MtFcotXNTEeZeOr2MsJ4-eMc-GTZkHSx2NT-yLYEvxvCn24sUwCsikZ2z4xKpZ6zCilkAWdnbhCXux-c2Q4-MMzRrQf0DACX-wadJg34KmBdg4Bu1qnIExQsNnw7WtDbDg9Vvs6BHxKXKCafZkUfDVsP9PJytmKssvt8bZkTcKIBhH0KRpLUjxJHvJBFHQWbP3G9e7Tl5_qOT0yYJxUi3JFC9N2U_-53rA_aMcrM6lKR6py0q0)。 28 | 29 | 如果看不到图,你可以尝试[这个链接](http://www.plantuml.com/plantuml/uml/bP31QlCm48JF_pw5o3s7Fxc546ARbXhIrBUUXIKS4MbbfDr2A7ttsb6etPe4R30icc-6sOLz91c8dGM8PuEj3DA9sieOnIVyosLzRe9dL8MtFcotXNTEeZeOr2MsJ4-eMc-GTZkHSx2NT-yLYEvxvCn24sUwCsikZ2z4xKpZ6zCilkAWdnbhCXux-c2Q4-MMzRrQf0DACX-wadJg34KmBdg4Bu1qnIExQsNnw7WtDbDg9Vvs6BHxKXKCafZkUfDVsP9PJytmKssvt8bZkTcKIBhH0KRpLUjxJHvJBFHQWbP3G9e7Tl5_qOT0yYJxUi3JFC9N2U_-53rA_aMcrM6lKR6py0q0) 30 | 31 | 类型流程序由输入端口(InputEndpoint),输出端口(OutputEndpoint)和纯函数以及在函数间传递的标准类型和自定义类型构成。类型流的原则是**副作用剥离**,既业务逻辑应该全部在纯函数里, 32 | 副作用全部在输入输出端口里。 33 | 34 | 如下图里面最下方的AddAndPrint是违反原则的,应该避免。 35 | 36 | ![下图](http://www.plantuml.com/plantuml/png/bP31QW8n48RFpLC4xnwwtaGMTvUMBbYxjvx39gp1PB9CPWfI-kvTZRXMY-Xjo7pVpvyHoqWAYRqJSMkXLwYyR9sAKNwBfrArsmu3Www22xXOLC5x1NfbmTJGCxs0xeC5odDPChhTPmZwjbPW5nzH2sTt36z4_IFM1zFzSCDeNXbQiW46T6Mx3PMEpRzjo20eib-cKRGPw0gjZm74Zn3fR6lsXwRorhMERA5r9YyBZBi2T8C3Boy_T8BUsP6TNKU8KulfD9VhUZwlWKndK9pD-zsXmjLZYXNxNOAoCI3D8pl99kTTHN9Jp6pKnlceFV_LbWOqUoT-0G00) 37 | 38 | 回到前面的正确模型,这个模型很容易理解,这个应用让用户通过命令行输入整数,这个整数分别经过加2(Add2)和乘3(Multi3)的运算,然后再求和,最后打印出来。 39 | 40 | 理解模型后,我们用类型流提供的代码生成工具生成代码: 41 | 在工程根目录下,命令行输入命令: 42 | ``` 43 | ./genCode.sh typeflow/newModel_v1.puml 44 | ``` 45 | 一阵杂乱的日志信息后..^-^,代码就生成了,你应该能在src/main/java目录的com.github.notyy.example里找到`Add Add2 Multi3和Print`等纯函数和输出端口。 46 | 编辑代码,完成填空。 47 | 48 | 输入端口NumInput的代码生成在src/main/scala目录的相应包里了,当前版本暂时还无法生成Java的输入端口代码。。。 49 | 50 | 打开NumInput,可以看到代码已经全部填好了,代码的模板来自`code_template/scala/CommandLineInputEndpoint.scala`。 51 | 我们可以看到连调用链都自动生成了,这是因为模型已经包含了从输入端口到输入端口的整个流的所有入参出参和调用顺序信息。足够让类型流代码生成器自动生成调用链。 52 | 53 | 执行NumInput,按照提示输入数字,可以看到程序按照模型预期的结果执行。 54 | 55 | ### 添加状态 56 | 第一步的例子是个典型的map/reduce运算,正好契合函数式编程的好球区,但我们更普遍的业务应用是有状态的,那么要怎么处理状态呢? 57 | 58 | 打开`typeflow/newModel_v1_state.puml`,模型图如下: 59 | 60 | ![模型图](http://www.plantuml.com/plantuml/png/bPDlQzim4CPVxpw5wFiMbhuh9JH_LXRmkh2nxRMmX2CgdOnqrr76ldljYaZ6jGCE12vwVi_nw4w1XqWo43iB49yDjZ989sehOnJ_uDDMrizDy8ngvwqFz_Sxxvn4LHdK1soPlg1glK5QhyMMWttzuxq2qNqkEJUXYGlT6sjU6RyIjGkDtvaMyO_6wgMU8mVzo5YJyTZHbMbEjBMivwv2Eg0aywAFaawTOHUCYnwdyHywUWwrxcC4_nVXFq3QpnExzs68Isk1n6wKMebnqgBDISTWaCHuYbd-nfRCxMpVkcR7LfAU8oelLQ5-IyjQJAqW6txA5xt8C8BFmnHbhXdTczXAgayVmy9KpfkRkURGfro0vprUNw_5q-7kxe2a6nrWKFTQJWZdultqwlELs1To9AwI-0iDmRdebQdPJzqpiPkgz1_-o06A2_QZrFtJ-t2ONWxqMe9MMOT1dU_GWP0yoNvRy5ANE3tUVLFhGUU9nYLuAPDlLuofpixqlnYVJhrEUD2es5bu0m00) 61 | 62 | 可以看到和之前的模型相比增加了几个元素: 63 | 1. `Dispatch`, 由于输入端口遵循原则,除了输入一个整数外不含任何别的业务逻辑,所以增加了一个Dispatch函数,这个函数产生两个输出,一个是走原来的Add2和Multi3的流,没有任何改变。 另一个输出是Unit, 64 | 这是Scala的类型,相当于Java的`void`。 这里指示调用一个无参的文件输出端口:`LoadAccumulateValue` 65 | 2. `LoadAccumulateValue`是一个FileOutputEndpoint,所有对外部资源比如文件、数据库、消息队列等的访问(不论读还是写)都可以看做一个输出端口。这个函数加载一个累计值。 66 | 3. `Accumulate`函数把原来的Add的计算结果和累计值相加后,一个流传给原来的Print输出,另一个传给`SaveAccumulateValue`保存。 67 | 68 | 因此我们可以理解现在程序的逻辑是每次的计算结果会累加上次的计算结果。因此现在这是一个有状态的业务了。 69 | 70 | 同样,生成代码,**注意代码生成器不会覆盖已经存在的文件**,因此原来的业务逻辑代码保留不动,但是NumInput要删除以便生成新的调用链(将来的版本会实现不用删除文件情况下对调用链的更新。): 71 | ``` 72 | ./genCode.sh typeflow/newModel_v1_state.puml 73 | ``` 74 | 75 | 再次填空,注意Dispatch的代码如下: 76 | ``` 77 | public class Dispatch { 78 | public Integer execute(Integer param1) { 79 | return param1; 80 | } 81 | } 82 | ``` 83 | 那个Unit类型无需返回。 84 | 85 | 注意LoadAccumulateValue和SaveAccumulateValue是根据同一个写文本文件的代码模板生成的,没有给读写提供不同的模板。 86 | 因此会需要一些修改: 87 | 88 | `LoadAccumulateValue`第11行,修改保存累计值的文本文件的路径,因为代码生成器不知道该值。 89 | ``` 90 | File file = new File("./localoutput/accu.txt"); 91 | ``` 92 | 如果你没耐心自己慢慢写,在ref_code目录下有我的参考实现,但我已经好几年没写Java,所以应该已经不是fashion的写法了。如果你能contribute最新的写法更好。 93 | 94 | 然后是`SaveAccumulateValue`,第11行务必用相同的文件路径。 95 | 然后**注意**第15行的true改成false,这样就只会覆盖同行累计值。 96 | 97 | 看一下NumInput,新的调用链已经生成好了,可以检查一下有没有问题。 98 | 99 | 现在,执行! 100 | 101 | 你可以看到再次输入的结果已经变化,因为已经把累计值计算进去了,`localoutput/accu.txt`也正确记录了累计值。 102 | 103 | 好,我们已经知道了怎么处理状态,接下来再通过后面几步来体会类型流的优势。 104 | 105 | ### 观察程序行为 106 | 类型流的模型乍一看和工作流有点像,但两者有本质的不同:工作流的每一个**步骤**都可以做任何事,比如读写文件、访问数据库等。 107 | 随便向工作流里加入一个步骤,你是无法知道这个步骤对整个系统行为的影响范围的。步骤之间可能互相干扰,不打开代码检查无法知道。 108 | 109 | 而类型流的每一个节点都只能通过输出流来影响系统行为,即使是有副作用的输出端口,你也可以很清楚的知道它的影响范围在哪里。纯函数更加如此了。 110 | 111 | 我们对之前两步例子的结果继续做扩展来说明这个优势。 112 | 113 | 打开`newModel_v1_record.puml`,应该看到如下图: 114 | 115 | ![下图](http://www.plantuml.com/plantuml/png/bPDDQnin48RFdLyXSa-WpYq44tyqrE2c9lteMQo4BIAD5ccK4ah_UtOZhmR4hc4DnkETvnszCwE5XqWoK78M8BuOR6MGJZHMnYZ-mQUrR5wEmITK6szzkB_VUESaghr1lSCsqHTKhJT8owLOfs3V_dYlG7IVIyfLECQPwfKrRmc_4hKpXTyJpU8VZTLpDOSKz6F2Xep7ZjwRuudMDkcfLaW7b6HUzK7IgHDiWd7HupZ-hADFeT6zZU3_8lm7oFjvIkvZ6EBIMixiIgc5g9roRYfr20P9r5CiwM_9ahcThDiqoIKhIKyHUoyLKx-bJLgChQ08ViXNFKjGmSCm1S-re5iJMqdDQShiisvyK9UF-waZ5OvF01ZhzEPcoXxqVSx0oqsulbwAfztTtGv97tK0ZiD7xkhprRaFo3zzLJPTysKcC0IbofR9Ahd8oLze28QWpunjUsmkcLtjkNx-KxN1AjXMhllBNZyytrNeSnbQy04SXz931qBMPFajunopdHxllhaqennoOJREOTPjM5ZYhCnlOlrqx7rWXiySTlwWKILduHq0) 116 | 117 | 跟之前的模型对比可知,只增加了一个Record输出端口,注意由于plantuml的自动布局特性,`LoadAccumulateValue`被画到左边去了,但实际上模型没有大改。 118 | 119 | 增加的这个Record,根据入参可知,它记录每次用户输入和计算的最终结果。 仅仅通过模型,我们就可以非常确定程序原有的功能没有收到任何影响。 120 | 121 | 废话少说,生成代码,记得要删除NumInput以便生成新的调用链,别的都不用改。 122 | ``` 123 | ./genCode.sh typeflow/newModel_v1_record.puml 124 | ``` 125 | 第11行改成 126 | ``` 127 | File file = new File("./localoutput/record.txt"); 128 | ``` 129 | 第15行改成 130 | ``` 131 | writer.write(param1.toString() + "," + param2.toString()); 132 | ``` 133 | 另外就是把localoutput/accu.txt记录的之前的累计值删除,以方便我们干净的重来。 134 | 135 | ok,执行以下,输入1,2,3后,我的record.txt文件记录了如下内容: 136 | ``` 137 | 1,6 138 | 2,16 139 | 3,30 140 | ``` 141 | 你呢? 142 | 143 | ### 回放测试 144 | 在前一步的练习中,我们记录下了每次用户输入的整数和程序执行后存储到累计值中的结果。 145 | 我们现在可以重放这个记录来对我们的程序做回归测试。 146 | 147 | 打开`newModel_v1_replay.puml`查看,如下图所示: 148 | ![下图](http://www.plantuml.com/plantuml/png/bLFVQzim47xU_HMYxoqiVGibDF5M5l0oan_Run2sS4HboSYdZZBslq-UOfznPBCK6f_rVT_TvxiJF3V8BRfA0JIlKjVSyehbYZSD-mqVDZn_BQrnkaYCClRfAJ5LnNMHIYsskdRuMHUraHfNAxPnLht_CAQ8o5wack4p_pTNQMtBvaRrMibtfxyWrZSAlpcyYPyvArwSpb4QxTcfBC-uKKvCsK9XDKVv9ZAEPoz8hAmQe-FqlMYCixbeRLZpIyCVW32RdQl-VHYR6yc5kowA1Hix9MmAognFmgWZewnVdKBvE9atprtb54SnnKWDBuZrdIidfeiT-9luR_A8s3MHKJzxwYVPr1ppyvp9esXmBt9Z2qQB7B5OAubJUIX7p8MVOCsjc1t4XUErJaMd0h66FZnCTGDYBPsXzmjtVxzYk-rokTKeIc5XrA8MJe_xvVA0tdOvXdq5UeC1iHM67aBx6-hSVRnZXopzkTrbfoEL710MWR1EaXFXrv8-MpTms6rdm2uWXDWZS170avO8NT0YiVsEZIXmrodbfvU1DL91y49mUxsl3iFjX1OBf0KL6CJzDtWY14sGFnATOhBJqlr_I3t874bCnYaCkeI48UwLOUMlJYChInnorzqdeCqOk-eOuHdyR8nHWckuFDkpoDM3Pw4BLodu2m00) 149 | 150 | 和`newModel_v1_record.puml`相比,可以看到我们增加了一个新的输入端口`LoadTestRecord` 151 | 152 | 继续贯彻输入端口不做业务处理的原则,这个输入端口只是逐行读取前面记录的测试数据,每行触发一次数据流执行。 153 | 154 | 输入的字符串交给Split函数,拆分成输入值和期望值两个整数,输入值传给`Dispatch`函数,从而触发原本的业务流程。期望值传给`Compare`函数。 155 | `Compare`函数同时接受原本业务流程最终计算出的累计值,然后在进行比较,输出Bool值,然后打印输出。 156 | 157 | 可以看出这个模型相当于给原本需要用户手工逐行输入的业务流程增加了一个自动重放入参的入口,然后比较预期的输出结果,从而达到回归测试的效果。 158 | 159 | 闲话少说,生成代码 160 | ``` 161 | ./genCode.sh typeflow/newModel_v1_replay.puml 162 | ``` 163 | 继续填空,`Compare`很简单,只是要注意用`equals`而不是`==`, `PrintCompareResult`也很简单,把参数填上就好了。 164 | 165 | `Split`略为复杂,因为Split要返回两个整数,分别给不同的数据流,而Java不支持元组,所以我这里用[vavr库](https://www.vavr.io/vavr-docs/#_tuples)来实现。 166 | 请参考ref_code/Split.java 167 | 168 | `LoadTestRecord`生成在scala工程里,略做修改,第15行,输入上一步练习记录数据的文件路径`./localoutput/record.txt`, 169 | 修改第17行,把toInt去掉。如果`./localoutput/accu.txt`里面有值,**记得清掉**。 170 | 171 | 现在执行`LoadTestRecord`,即可运行。 172 | 173 | ### 实现阿里云函数计算版本 174 | 先声明,本人没有收阿里云的广告费~~~,只是接触的最早,用起来又很方便,所以先出了阿里云的版本。 将来肯定还要支持aws lambda,华为云等多种云平台的。 175 | 176 | 在这个例子里,我用了阿里云函数计算服务(function computing)和对象存储服务(Object Storage Service —— OSS),oss因为极其便宜,所以特别适合用来做实现,mysql有点小贵。 177 | 178 | 阿里云资源的购买和配置我这里就不多说了,请自行查阅阿里云的文档。 在本地需要配置好[ossutil](https://helpcdn.aliyun.com/document_detail/50452.html)和[fun](https://help.aliyun.com/document_detail/64204.html). 179 | 配置好你的阿里云账户。注意用fun需要使用你的root账户的ACCESS_KEY等,不能用另建的安全账户,否则会出错。 180 | 181 | 另外,我准备了[一段视频](https://www.bilibili.com/video/av77104359/),你如果只是想了解操作的过程和效果,看一下视频也可以。 182 | 183 | 如果希望亲手手操,并且也准备好了的环境的,请跟我继续。 184 | 185 | 打开typeflow/newModel_v1_state_aliyun.puml,因看看见如下的图: 186 | ![下图](http://www.plantuml.com/plantuml/png/bP9VQzim5CMVTp-5w7iBIzzA2SsVbGMojYniUnQMI4IbAoFTsr76llliQlODYHrm873iStxoyRs6Xqmw4GSFa5yTrZhfe2glSrP_uDDILw_x51dDAlgOxkzNCGIDPklGRh1c-eAcZWvfFbTBJlxzLum6LFExH2xIFa-a5zwzCtubgYU2pqoJye_EMs6cb7lUxQQ2Bvpi8sD5BcAJthhzWPQUxTXZo2RKvDxiYwP30woI2pdYSFoxpP5JPTiQAVv6-0jG5ll4uMEAwi9S_qSNnipKQL7nngnlxCdTZe9LnO6z9ZkMs4Pj9FLJUxPgVCuTjTDlTZfrGdHZKpB1FhfskTPK7Svhmr3Zcvknmw6jEY7UR-pwUgwUDdTt6oIxjmcETElr3Q4J7tUlFb-fjIHFp5anVw0Zw4Jh36rXw3rBTh6Lyuyl-W92mVgaTVkLdTqitn2qNWdDv057kd-79WY5AVYjn4LBdJRU_ClH4XKf1qjnaOLR2IYwaDtApeo1rSE3HSF1mpy0) 187 | 188 | 这个模型是基于typeflow/newModel_v1_state.puml修改。对比[第三步:添加状态](#添加状态)的模型图,我们可以发现主要有三个改变: 189 | 1. NumInput从\<>改成了\<>,这是阿里云提供的http request/response类型的触发器。 190 | 2. Print输出端口去掉了,因为在云端运行的时候终端打印没有什么意义。取而代之的是原本流向Print的AC::Integer现在改为流回到NumInput,以便完成一个Request/Response。 191 | 3. LoadAccumulateValue和SaveAccumulateValue两个输出端口从\<>改成了\<>。它们的逻辑改为用OSS来做存储和读取。 192 | 193 | 可以看到除了输入输出端口的修改外,内部的纯函数(业务逻辑)没有任何需要修改。那些不变的东西就是我们的领域模型。大家如果了解[端口适配器架构](https://herbertograca.com/2017/09/14/ports-adapters-architecture/),就会发现我这个类型流就是一种端口适配器架构。 194 | ``` 195 | ./genCodeAliyun.sh typeflow/newModel_v1_state_aliyun.puml 196 | ``` 197 | 打开java目录可以看到原来的代码都在,代码生成工具并**不会自动删除图上没有的代码**。 198 | 199 | 在scala目录下多了个aliyun目录,里面是对Java函数的包装,因为阿里云函数计算要求函数都要继承`StreamRequestHandler`,实现其中的 200 | ```scala 201 | override def handleRequest(input: InputStream, output: OutputStream, context: Context): Unit = { 202 | } 203 | ``` 204 | 这个方法。 类型流代码生成器通过生成适配器来避免业务代码和部署平台的耦合。 205 | 206 | 大部分代码无需修改。 只有`LoadAccumulateValueHandler`和`SaveAccumulateValueHandler`这两个OSS输出端口是需要实现的。 207 | 而自动生成的`SaveAccumulateValueHandler`已经很好的完成了功能,无需修改,唯一要改的就是`LoadAccumulateValueHandler`了。 208 | 打开生成的代码可以看到已经生成包含ossClient在内的代码骨架,具体的代码可以参考`ref_code/LoadAccumulateValueHandler`。 209 | 210 | 需要注意的是代码里的这几行: 211 | ```scala 212 | val accessKey = System.getenv("ACCESS_KEY") 213 | val accessSecretKey = System.getenv("SECRET_KEY") 214 | val accountId = System.getenv("ACCOUNT_ID") 215 | val bucketName = System.getenv("BUCKET_NAME") 216 | val objectName = System.getenv("OBJECT_NAME") 217 | ``` 218 | 这几行是读取环境变量,需要你的阿里云管理控制台的相应函数的配置界面配置这些环境变量。 219 | 220 | 除了这些代码之外,类型流代码生成器还自动生成了工程根目录下的`template.yml`, `fun`命令行工具会根据这个配置文件来向阿里云部署应用。 221 | 222 | 确认代码无误后,运行`sbt assembly`编译打包代码,`sbt assembly`会把所有编译后的代码和第三方库一起打成一个大jar包(fat jar),这样部署到云端后就不用多管依赖库的问题了。 223 | 224 | 打包完成后,运行`./deploy-aliyun.sh`把应用部署到云端。 225 | 226 | 另外,`LoadAccumulateValueHandler`需要读取OSS存储内容,如果读不到会报错。所以我们先把包含初始数据的`accu.txt`传上去。 227 | ```scala 228 | 229 | ``` 230 | 231 | ### 异常处理 232 | 233 | ### 自定义类型和中文建模 234 | -------------------------------------------------------------------------------- /accu.txt: -------------------------------------------------------------------------------- 1 | {"value":0} -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | 3 | name := "scala_template_typeflow" 4 | 5 | version := "0.0.1" 6 | 7 | isSnapshot := true 8 | 9 | organization := "com.github.notyy" 10 | 11 | // set the Scala version used for the project 12 | scalaVersion := "2.13.0" 13 | 14 | resolvers ++= Seq( 15 | "Sonatype Releases" at "http://oss.sonatype.org/content/repositories/releases", 16 | "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots" 17 | ) 18 | 19 | libraryDependencies ++= Seq( 20 | "org.pegdown" % "pegdown" % "1.6.0" % "test", //used in html report 21 | "org.scalactic" %% "scalactic" % "3.0.8", 22 | "org.scalatest" %% "scalatest" % "3.0.8" % "test", 23 | "org.slf4j" % "slf4j-api" % "1.7.7", 24 | "ch.qos.logback" % "logback-classic" % "1.2.3", 25 | "com.typesafe.scala-logging" %% "scala-logging" % "3.9.2", 26 | "org.seleniumhq.selenium" % "selenium-java" % "2.35.0" % "test", 27 | "org.json4s" %% "json4s-native" % "3.6.7", 28 | "org.typelevel" %% "cats-core" % "2.0.0", 29 | "com.aliyun" % "aliyun-java-sdk-fc" % "1.4.0", 30 | "com.aliyun.fc.runtime" % "fc-java-core" % "1.3.0", 31 | "com.aliyun.oss" % "aliyun-sdk-oss" % "3.6.0", 32 | "io.vavr" % "vavr" % "0.9.3" 33 | ) 34 | 35 | logBuffered := false 36 | 37 | // reduce the maximum number of errors shown by the Scala compiler 38 | maxErrors := 20 39 | 40 | // increase the time between polling for file changes when using continuous execution 41 | pollInterval := scala.concurrent.duration.FiniteDuration(1000L, "ms") 42 | 43 | // append several options to the list of options passed to the Java compiler 44 | javacOptions ++= Seq("-source", "1.8", "-target", "1.8") 45 | 46 | // append -deprecation to the options passed to the Scala compiler 47 | scalacOptions += "-deprecation" 48 | 49 | // disable updating dynamic revisions (including -SNAPSHOT versions) 50 | offline := true 51 | 52 | // set the prompt (for this build) to include the project id. 53 | shellPrompt in ThisBuild := { state => Project.extract(state).currentRef.project + "> " } 54 | 55 | // set the prompt (for the current project) to include the username 56 | shellPrompt := { state => System.getProperty("user.name") + "> " } 57 | 58 | // disable printing timing information, but still print [success] 59 | showTiming := false 60 | 61 | // disable printing a message indicating the success or failure of running a task 62 | showSuccess := false 63 | 64 | // change the format used for printing task completion time 65 | timingFormat := { 66 | import java.text.DateFormat 67 | DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT) 68 | } 69 | 70 | // only use a single thread for building 71 | parallelExecution := false 72 | 73 | 74 | // Use Scala from a directory on the filesystem instead of retrieving from a repository 75 | //scalaHome := Some(file("/home/user/scala/trunk/")) 76 | 77 | // don't aggregate clean (See FullConfiguration for aggregation details) 78 | aggregate in clean := false 79 | 80 | // only show warnings and errors on the screen for compilations. 81 | // this applies to both test:compile and compile and is Info by default 82 | logLevel in compile := Level.Info 83 | 84 | // only show warnings and errors on the screen for all tasks (the default is Info) 85 | // individual tasks can then be more verbose using the previous setting 86 | logLevel := Level.Info 87 | 88 | // only store messages at info and above (the default is Debug) 89 | // this is the logging level for replaying logging with 'last' 90 | persistLogLevel := Level.Info 91 | 92 | // only show 10 lines of stack traces 93 | traceLevel := 10 94 | 95 | exportJars := true 96 | 97 | // only show stack traces up to the first sbt stack frame 98 | traceLevel := 0 99 | 100 | // add SWT to the unmanaged classpath 101 | // unmanagedJars in Compile += file("/usr/share/java/swt.jar") 102 | 103 | // seq(oneJarSettings: _*) 104 | 105 | // libraryDependencies += "commons-lang" % "commons-lang" % "2.6" 106 | 107 | // jacoco.settings 108 | 109 | // Execute tests in the current project serially 110 | // Tests from other projects may still run concurrently. 111 | parallelExecution in Test := false 112 | 113 | // create beautiful scala test report 114 | testOptions in Test ++= Seq( 115 | Tests.Argument(TestFrameworks.ScalaTest,"-h","target/html-unit-test-report"), 116 | Tests.Argument(TestFrameworks.ScalaTest,"-u","target/unit-test-reports"), 117 | Tests.Argument(TestFrameworks.ScalaTest,"-o"), 118 | Tests.Argument(TestFrameworks.ScalaTest,"-l","FunctionTest") 119 | ) 120 | 121 | // testOptions in jacoco.Config ++= Seq( 122 | // Tests.Argument(TestFrameworks.ScalaTest,"-h","target/html-unit-test-report"), 123 | // Tests.Argument(TestFrameworks.ScalaTest,"-u","target/unit-test-reports"), 124 | // Tests.Argument(TestFrameworks.ScalaTest,"-o"), 125 | // Tests.Argument(TestFrameworks.ScalaTest,"-l","FunctionTest") 126 | // ) 127 | 128 | testOptions += Tests.Argument(TestFrameworks.JUnit, "-q", "-v") 129 | 130 | // packAutoSettings 131 | 132 | scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature","-language:higherKinds","-language:implicitConversions") 133 | 134 | assemblyMergeStrategy in assembly := { 135 | case PathList("META-INF", xs @ _*) => MergeStrategy.discard 136 | case x => MergeStrategy.first 137 | } -------------------------------------------------------------------------------- /code_template/java/FileOutputEndpointTemplate.java: -------------------------------------------------------------------------------- 1 | package $PackageName$; 2 | 3 | import java.io.BufferedWriter; 4 | import java.io.File; 5 | import java.io.FileWriter; 6 | import java.io.IOException; 7 | 8 | public class $DefinitionName$ { 9 | public $ReturnType$ execute($Params$) { 10 | //TODO give me path 11 | File file = new File("./give-me-path"); 12 | BufferedWriter writer = null; 13 | try { 14 | //using append mode, if you need overwritten mode, set false. 15 | writer = new BufferedWriter(new FileWriter(file, true)); 16 | writer.write(param1.toString()); 17 | writer.newLine(); 18 | writer.flush(); 19 | } catch (IOException e) { 20 | e.printStackTrace(); 21 | }finally { 22 | if(writer != null) { 23 | try { 24 | writer.close(); 25 | } catch (IOException e) { 26 | e.printStackTrace(); 27 | } 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /code_template/java/OutputEndpoint.java: -------------------------------------------------------------------------------- 1 | package $PackageName$; 2 | 3 | public class $DefinitionName$ { 4 | public $ReturnType$ execute($Params$) { 5 | System.out.println(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /code_template/java/PureFunction.java: -------------------------------------------------------------------------------- 1 | package $PackageName$; 2 | 3 | public class $DefinitionName$ { 4 | public $ReturnType$ execute($Params$) { 5 | return null; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /code_template/scala/AliyunHttpInputEndpoint.scala: -------------------------------------------------------------------------------- 1 | package $PackageName$.aliyun 2 | 3 | import com.aliyun.fc.runtime.{Context, HttpRequestHandler} 4 | import com.aliyun.oss.{OSS, OSSClientBuilder} 5 | import com.aliyuncs.fc.client.FunctionComputeClient 6 | import com.github.notyy.typeflow.util.{JSONUtil, Param, AliyunUtil} 7 | import javax.servlet.http.{HttpServletRequest, HttpServletResponse} 8 | 9 | import scala.io.Source 10 | import scala.util.{Failure, Success} 11 | 12 | class $DefinitionName$Handler extends HttpRequestHandler{ 13 | private var result: Option[Param[Object]] = None 14 | 15 | override def handleRequest(request: HttpServletRequest, response: HttpServletResponse, context: Context): Unit = { 16 | val accessKey = System.getenv("ACCESS_KEY") 17 | val accessSecretKey = System.getenv("SECRET_KEY") 18 | val accountId = System.getenv("ACCOUNT_ID") 19 | 20 | //TODO region should not be hardcoded 21 | val fcClient = new FunctionComputeClient("cn-shanghai", accountId, accessKey, accessSecretKey) 22 | val ossClient: OSS = new OSSClientBuilder().build("oss-cn-shanghai-internal.aliyuncs.com", accessKey, accessSecretKey) 23 | val source = Source.fromInputStream(request.getInputStream) 24 | JSONUtil.fromJSON[Param[$Params$]](source.mkString).map{ input => 25 | $CallingChain$ 26 | } match { 27 | case Success(value) => { 28 | if(result.isDefined) { 29 | response.setStatus(200) 30 | response.getWriter.println(JSONUtil.toJSON(result.get)) 31 | } else { 32 | val successResult = "success complete, but no result" 33 | response.setStatus(200) 34 | response.getWriter.println(successResult) 35 | println(successResult) 36 | } 37 | } 38 | case Failure(exception) => { 39 | response.getWriter.println(exception.getMessage) 40 | response.setStatus(500) 41 | } 42 | } 43 | } 44 | 45 | def setResponse(param: Param[Object]): Unit = { 46 | this.result = Some(param) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /code_template/scala/AliyunOSSOutputEndpointTemplate.scala: -------------------------------------------------------------------------------- 1 | package $PackageName$.aliyun 2 | 3 | import java.io.{ByteArrayInputStream, InputStream, OutputStream} 4 | 5 | import com.aliyun.fc.runtime.{Context, StreamRequestHandler} 6 | import com.aliyun.oss.OSSClientBuilder 7 | import com.github.notyy.typeflow.util.{JSONUtil, Param} 8 | 9 | import scala.io.Source 10 | import scala.util.{Failure, Success, Try} 11 | 12 | class $DefinitionName$Handler extends StreamRequestHandler { 13 | override def handleRequest(input: InputStream, output: OutputStream, context: Context): Unit = { 14 | val accessKey = System.getenv("ACCESS_KEY") 15 | val accessSecretKey = System.getenv("SECRET_KEY") 16 | val accountId = System.getenv("ACCOUNT_ID") 17 | val bucketName = System.getenv("BUCKET_NAME") 18 | val objectName = System.getenv("OBJECT_NAME") 19 | 20 | Try { 21 | val ossClient = new OSSClientBuilder().build("oss-cn-shanghai-internal.aliyuncs.com", accessKey, accessSecretKey) 22 | val inStr = Source.fromInputStream(input).mkString 23 | ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(inStr.getBytes)) 24 | } match { 25 | case Success(value) => $WriteOutput$ 26 | case Failure(exception) => output.write(exception.getMessage.getBytes) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /code_template/scala/CommandLineArgsInputEndpoint.scala: -------------------------------------------------------------------------------- 1 | package com.github.notyy.typeflow.tooling 2 | 3 | import java.io.File 4 | 5 | import com.github.notyy.typeflow.domain.{AliyunHttpInputEndpoint, Definition} 6 | import com.github.notyy.typeflow.editor.Model2Code.{CodeContent, CodeFileName} 7 | import com.github.notyy.typeflow.editor.aliyun.{AliyunConfigGen, AliyunFunction, Trigger} 8 | import com.github.notyy.typeflow.editor.{CodeLang, Model2Code, Path, PlantUML, PlantUML2Model, ReadFile, SaveToFile} 9 | import com.typesafe.scalalogging.Logger 10 | 11 | import scala.util.{Failure, Success} 12 | 13 | object CommandLineArgsInputEndpoint extends App { 14 | private val logger = Logger(CommandLineArgsInputEndpoint.getClass) 15 | if (args.length != 5) { 16 | println("usage: genCode {modelFilePath} {outputPath} {lang} {packageName} {platform}") 17 | } 18 | execute(args(0), args(1), args(2), args(3), args(4), args(5)) 19 | 20 | def execute(modelFilePath: String, outputPath: String, lang: String, packageName: String, platform: String, codeUri: String): GenCodeReq = { 21 | ??? 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /code_template/scala/CommandLineInputEndpoint.scala: -------------------------------------------------------------------------------- 1 | package $PackageName$ 2 | 3 | import com.typesafe.scalalogging.Logger 4 | 5 | import scala.io.StdIn 6 | import scala.util.{Failure, Success, Try} 7 | 8 | object $DefinitionName$ extends App { 9 | private val logger = Logger($DefinitionName$.getClass) 10 | 11 | execute() 12 | 13 | @scala.annotation.tailrec 14 | def execute(): Unit = { 15 | println("input an integer") 16 | val input = StdIn.readLine().toInt 17 | Try { 18 | $CallingChain$ 19 | } match { 20 | case Success(value) => { 21 | logger.info(s"processing input: $input successfully complete") 22 | } 23 | case Failure(exception) => { 24 | logger.error("error occurs", exception) 25 | } 26 | } 27 | execute() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /code_template/scala/FileInputEndpointTemplate.scala: -------------------------------------------------------------------------------- 1 | package $PackageName$ 2 | 3 | import com.typesafe.scalalogging.Logger 4 | 5 | import scala.io.{Source, StdIn} 6 | import scala.util.{Failure, Success, Try} 7 | 8 | object $DefinitionName$ extends App { 9 | private val logger = Logger($DefinitionName$.getClass) 10 | 11 | execute() 12 | 13 | def execute(): Unit = { 14 | //TODO input your file name 15 | val source = Source.fromFile() 16 | source.getLines.foreach { rawInput => 17 | val input = rawInput.toInt 18 | Try { 19 | $CallingChain$ 20 | } match { 21 | case Success(value) => { 22 | logger.info(s"processing input: $input successfully complete") 23 | } 24 | case Failure(exception) => { 25 | logger.error("error occurs", exception) 26 | } 27 | } 28 | } 29 | source.close() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /code_template/scala/StreamRequestHandlerTemplate.scala: -------------------------------------------------------------------------------- 1 | package $PackageName$.aliyun 2 | 3 | import java.io.{InputStream, OutputStream} 4 | 5 | import com.aliyun.fc.runtime.{Context, StreamRequestHandler} 6 | import $PackageName$.$DefinitionName$ 7 | import com.github.notyy.typeflow.util.{JSONUtil, JSonFormats, Param} 8 | 9 | import scala.io.Source 10 | import scala.util.{Failure, Success} 11 | 12 | class $DefinitionName$Handler extends StreamRequestHandler { 13 | override def handleRequest(input: InputStream, output: OutputStream, context: Context): Unit = { 14 | val inStr = Source.fromInputStream(input).mkString 15 | JSONUtil.fromJSON[Param[$Params$]](inStr).map { param => 16 | $Callee$.execute($ParamCall$) 17 | } match { 18 | case Success(value) => $WriteOutput$ 19 | case Failure(exception) => output.write(exception.getMessage.getBytes) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /deploy-aliyun.sh: -------------------------------------------------------------------------------- 1 | #copy flow puml file to oss 2 | #ossutil cp $1 oss://type-flow -f 3 | #copy code jar package to oss 4 | ossutil cp target/scala-2.13/scala_template_typeflow-assembly-0.0.1.jar oss://type-flow -f 5 | #deploy to aliyun function compute 6 | fun deploy -------------------------------------------------------------------------------- /genCode.sh: -------------------------------------------------------------------------------- 1 | java -cp ./type-flow-assembly-0.0.1.jar com.github.notyy.typeflow.editor.GenCodeScript $1 . java com.github.notyy.example local no -------------------------------------------------------------------------------- /genCodeAliyun.sh: -------------------------------------------------------------------------------- 1 | java -cp ./type-flow-assembly-0.0.1.jar com.github.notyy.typeflow.editor.GenCodeScript $1 . java com.github.notyy.example aliyun oss://type-flow/scala_template_typeflow-assembly-0.0.1.jar -------------------------------------------------------------------------------- /lib/type-flow_2.13-0.0.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notyy/scala_template_typeflow/772db191c74d71424d1f0f06e2e1a7e5ba730689/lib/type-flow_2.13-0.0.1.jar -------------------------------------------------------------------------------- /localoutput/accu.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notyy/scala_template_typeflow/772db191c74d71424d1f0f06e2e1a7e5ba730689/localoutput/accu.txt -------------------------------------------------------------------------------- /localoutput/record.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notyy/scala_template_typeflow/772db191c74d71424d1f0f06e2e1a7e5ba730689/localoutput/record.txt -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.3.0 -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.10") -------------------------------------------------------------------------------- /ref_code/LoadAccumulateValue.java: -------------------------------------------------------------------------------- 1 | package com.github.notyy.example; 2 | 3 | import java.io.*; 4 | 5 | public class LoadAccumulateValue { 6 | public Integer execute() { 7 | //TODO give me path 8 | File file = new File("./localoutput/accu.txt"); 9 | BufferedReader reader = null; 10 | try { 11 | //using append mode, if you need overwritten mode, set false. 12 | reader = new BufferedReader(new FileReader(file)); 13 | String s = reader.readLine(); 14 | if(s == null || s.trim().isEmpty()) { 15 | return 0; 16 | }else{ 17 | return Integer.valueOf(s); 18 | } 19 | } catch (IOException e) { 20 | e.printStackTrace(); 21 | }finally { 22 | if(reader != null) { 23 | try { 24 | reader.close(); 25 | } catch (IOException e) { 26 | e.printStackTrace(); 27 | } 28 | } 29 | } 30 | return 0; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ref_code/LoadAccumulateValueHandler.scala: -------------------------------------------------------------------------------- 1 | package com.github.notyy.example.aliyun 2 | 3 | import java.io.{InputStream, OutputStream} 4 | 5 | import com.aliyun.fc.runtime.{Context, StreamRequestHandler} 6 | import com.aliyun.oss.OSSClientBuilder 7 | 8 | import scala.io.Source 9 | import scala.util.{Failure, Success, Try} 10 | 11 | class LoadAccumulateValueHandler extends StreamRequestHandler { 12 | override def handleRequest(input: InputStream, output: OutputStream, context: Context): Unit = { 13 | val accessKey = System.getenv("ACCESS_KEY") 14 | val accessSecretKey = System.getenv("SECRET_KEY") 15 | val accountId = System.getenv("ACCOUNT_ID") 16 | val bucketName = System.getenv("BUCKET_NAME") 17 | val objectName = System.getenv("OBJECT_NAME") 18 | 19 | Try { 20 | val ossClient = new OSSClientBuilder().build("oss-cn-shanghai-internal.aliyuncs.com", accessKey, accessSecretKey) 21 | val ossObject = ossClient.getObject(bucketName, objectName) 22 | val rs = Source.fromInputStream(ossObject.getObjectContent).mkString 23 | ossObject.close() 24 | rs 25 | } match { 26 | case Success(value) => output.write(value.getBytes) 27 | case Failure(exception) => output.write(exception.getMessage.getBytes) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ref_code/SaveAccumulateValue.java: -------------------------------------------------------------------------------- 1 | package com.github.notyy.example; 2 | 3 | import java.io.BufferedWriter; 4 | import java.io.File; 5 | import java.io.FileWriter; 6 | import java.io.IOException; 7 | 8 | public class SaveAccumulateValue { 9 | public void execute(Integer param1) { 10 | //TODO give me path 11 | File file = new File("./localoutput/accu.txt"); 12 | BufferedWriter writer = null; 13 | try { 14 | //using append mode, if you need overwritten mode, set false. 15 | writer = new BufferedWriter(new FileWriter(file, false)); 16 | writer.write(param1.toString()); 17 | writer.newLine(); 18 | writer.flush(); 19 | } catch (IOException e) { 20 | e.printStackTrace(); 21 | }finally { 22 | if(writer != null) { 23 | try { 24 | writer.close(); 25 | } catch (IOException e) { 26 | e.printStackTrace(); 27 | } 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /ref_code/Split.java: -------------------------------------------------------------------------------- 1 | package com.github.notyy.example; 2 | 3 | import io.vavr.Tuple; 4 | import io.vavr.Tuple2; 5 | 6 | public class Split { 7 | public Tuple2 execute(String param1) { 8 | String[] splits = param1.split(","); 9 | Integer input = Integer.valueOf(splits[0]); 10 | Integer accu = Integer.valueOf(splits[1]); 11 | return Tuple.of(input,accu); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/scala/com/github/notyy/example/HelloWorld.scala: -------------------------------------------------------------------------------- 1 | package com.github.notyy.example 2 | 3 | object HelloWorld extends App { 4 | println("hello,world") 5 | } 6 | -------------------------------------------------------------------------------- /type-flow-assembly-0.0.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notyy/scala_template_typeflow/772db191c74d71424d1f0f06e2e1a7e5ba730689/type-flow-assembly-0.0.1.jar -------------------------------------------------------------------------------- /typeflow/README.md: -------------------------------------------------------------------------------- 1 | put your typeflow files here -------------------------------------------------------------------------------- /typeflow/newModel_v1.puml: -------------------------------------------------------------------------------- 1 | 2 | @startuml 3 | 4 | skinparam class { 5 | BackgroundColor<> BurlyWood 6 | BackgroundColor<> BurlyWood 7 | BackgroundColor<> BurlyWood 8 | BackgroundColor<> BurlyWood 9 | BackgroundColor<> LightBlue 10 | BackgroundColor<> Crimson 11 | } 12 | 13 | class NumInput <> 14 | class Add2 <> 15 | class Add <> 16 | class Print <> 17 | class Multi3 <> 18 | 19 | NumInput --> NI::Integer 20 | NI::Integer --> Add2 21 | NI::Integer --> Multi3 22 | Add2 --> A2::Integer 23 | A2::Integer --> "1" Add 24 | Multi3 --> M3::Integer 25 | M3::Integer --> "2" Add 26 | Add --> A::Integer 27 | A::Integer --> Print 28 | @enduml 29 | -------------------------------------------------------------------------------- /typeflow/newModel_v1_diff.puml: -------------------------------------------------------------------------------- 1 | 2 | @startuml 3 | 4 | skinparam class { 5 | BackgroundColor<> BurlyWood 6 | BackgroundColor<> BurlyWood 7 | BackgroundColor<> BurlyWood 8 | BackgroundColor<> BurlyWood 9 | BackgroundColor<> BurlyWood 10 | BackgroundColor<> LightBlue 11 | BackgroundColor<> Crimson 12 | BackgroundColor<> Brown 13 | } 14 | 15 | class NumInput <> 16 | class Add2 <> 17 | class Add <> 18 | class Print <> 19 | class Multi3 <> 20 | class Accumulate <> #LightGreen 21 | class LoadAccumulateValue <> #LightGreen 22 | class SaveAccumulateValue <> #LightGreen 23 | class AccumulateValueResource <> #LightGreen 24 | class Dispatch <> #LightGreen 25 | 26 | LoadAccumulateValue .. AccumulateValueResource 27 | SaveAccumulateValue .. AccumulateValueResource 28 | NumInput --> NI::Integer 29 | NI::Integer --> Dispatch 30 | Dispatch --> "1" DI::Integer 31 | Dispatch --> "2" DI::Unit 32 | DI::Unit --> LoadAccumulateValue 33 | LoadAccumulateValue --> LAL::Integer 34 | LAL::Integer --> "1" Accumulate 35 | DI::Integer --> Add2 36 | DI::Integer --> Multi3 37 | Add2 --> A2::Integer 38 | A2::Integer --> "2" Add 39 | Multi3 --> M3::Integer 40 | M3::Integer --> "1" Add 41 | Add --> A::Integer 42 | A::Integer --> "2" Accumulate 43 | Accumulate --> AC::Integer 44 | AC::Integer --> Print 45 | AC::Integer --> SaveAccumulateValue 46 | @enduml 47 | -------------------------------------------------------------------------------- /typeflow/newModel_v1_record.puml: -------------------------------------------------------------------------------- 1 | 2 | @startuml 3 | 4 | skinparam class { 5 | BackgroundColor<> BurlyWood 6 | BackgroundColor<> BurlyWood 7 | BackgroundColor<> BurlyWood 8 | BackgroundColor<> BurlyWood 9 | BackgroundColor<> BurlyWood 10 | BackgroundColor<> LightBlue 11 | BackgroundColor<> Crimson 12 | BackgroundColor<> Brown 13 | } 14 | 15 | class NumInput <> 16 | class Add2 <> 17 | class Add <> 18 | class Print <> 19 | class Multi3 <> 20 | class Accumulate <> 21 | class LoadAccumulateValue <> 22 | class SaveAccumulateValue <> 23 | class AccumulateValueResource <> 24 | class Dispatch <> 25 | class Record <> 26 | class TestRecord <> 27 | 28 | LoadAccumulateValue .. AccumulateValueResource 29 | SaveAccumulateValue .. AccumulateValueResource 30 | NumInput --> NI::Integer 31 | NI::Integer --> "1" Record 32 | Record .. TestRecord 33 | NI::Integer --> Dispatch 34 | Dispatch --> "1" DI::Integer 35 | Dispatch --> "2" DI::Unit 36 | DI::Unit --> LoadAccumulateValue 37 | LoadAccumulateValue --> LAL::Integer 38 | LAL::Integer --> "1" Accumulate 39 | DI::Integer --> Add2 40 | DI::Integer --> Multi3 41 | Add2 --> A2::Integer 42 | A2::Integer --> "2" Add 43 | Multi3 --> M3::Integer 44 | M3::Integer --> "1" Add 45 | Add --> A::Integer 46 | A::Integer --> "2" Accumulate 47 | Accumulate --> AC::Integer 48 | AC::Integer --> Print 49 | AC::Integer --> SaveAccumulateValue 50 | AC::Integer --> "2" Record 51 | @enduml 52 | -------------------------------------------------------------------------------- /typeflow/newModel_v1_replay.puml: -------------------------------------------------------------------------------- 1 | 2 | @startuml 3 | 4 | skinparam class { 5 | BackgroundColor<> BurlyWood 6 | BackgroundColor<> BurlyWood 7 | BackgroundColor<> BurlyWood 8 | BackgroundColor<> BurlyWood 9 | BackgroundColor<> BurlyWood 10 | BackgroundColor<> LightBlue 11 | BackgroundColor<> Crimson 12 | BackgroundColor<> Brown 13 | } 14 | 15 | class NumInput <> 16 | class Add2 <> 17 | class Add <> 18 | class Print <> 19 | class Multi3 <> 20 | class Accumulate <> 21 | class LoadAccumulateValue <> 22 | class SaveAccumulateValue <> 23 | class AccumulateValueResource <> 24 | class Dispatch <> 25 | class LoadTestRecord <> 26 | class Split <> 27 | class TestRecord <> 28 | class Compare <> 29 | class PrintCompareResult <> 30 | 31 | LoadAccumulateValue .. AccumulateValueResource 32 | SaveAccumulateValue .. AccumulateValueResource 33 | NumInput --> NI::Integer 34 | LoadTestRecord --> LTR::String 35 | LoadTestRecord .. TestRecord 36 | LTR::String --> Split 37 | Split --> "1" SPL_INPUT::Integer 38 | Split --> "2" SPL_ACCU::Integer 39 | SPL_INPUT::Integer --> "1" Dispatch 40 | NI::Integer --> "1" Dispatch 41 | Dispatch --> "1" DI::Integer 42 | Dispatch --> "2" DI::Unit 43 | DI::Unit --> LoadAccumulateValue 44 | LoadAccumulateValue --> LAL::Integer 45 | LAL::Integer --> "1" Accumulate 46 | DI::Integer --> Add2 47 | DI::Integer --> Multi3 48 | Add2 --> A2::Integer 49 | A2::Integer --> "2" Add 50 | Multi3 --> M3::Integer 51 | M3::Integer --> "1" Add 52 | Add --> A::Integer 53 | A::Integer --> "2" Accumulate 54 | Accumulate --> AC::Integer 55 | AC::Integer --> Print 56 | AC::Integer --> SaveAccumulateValue 57 | AC::Integer --> "1" Compare 58 | SPL_ACCU::Integer --> "2" Compare 59 | Compare --> Boolean 60 | Boolean --> PrintCompareResult 61 | @enduml 62 | -------------------------------------------------------------------------------- /typeflow/newModel_v1_state.puml: -------------------------------------------------------------------------------- 1 | 2 | @startuml 3 | 4 | skinparam class { 5 | BackgroundColor<> BurlyWood 6 | BackgroundColor<> BurlyWood 7 | BackgroundColor<> BurlyWood 8 | BackgroundColor<> BurlyWood 9 | BackgroundColor<> BurlyWood 10 | BackgroundColor<> LightBlue 11 | BackgroundColor<> Crimson 12 | BackgroundColor<> Brown 13 | } 14 | 15 | class NumInput <> 16 | class Add2 <> 17 | class Add <> 18 | class Print <> 19 | class Multi3 <> 20 | class Accumulate <> 21 | class LoadAccumulateValue <> 22 | class SaveAccumulateValue <> 23 | class AccumulateValueResource <> 24 | class Dispatch <> 25 | 26 | LoadAccumulateValue .. AccumulateValueResource 27 | SaveAccumulateValue .. AccumulateValueResource 28 | NumInput --> NI::Integer 29 | NI::Integer --> Dispatch 30 | Dispatch --> "1" DI::Integer 31 | Dispatch --> "2" DI::Unit 32 | DI::Unit --> LoadAccumulateValue 33 | LoadAccumulateValue --> LAL::Integer 34 | LAL::Integer --> "1" Accumulate 35 | DI::Integer --> Add2 36 | DI::Integer --> Multi3 37 | Add2 --> A2::Integer 38 | A2::Integer --> "2" Add 39 | Multi3 --> M3::Integer 40 | M3::Integer --> "1" Add 41 | Add --> A::Integer 42 | A::Integer --> "2" Accumulate 43 | Accumulate --> AC::Integer 44 | AC::Integer --> Print 45 | AC::Integer --> SaveAccumulateValue 46 | @enduml 47 | -------------------------------------------------------------------------------- /typeflow/newModel_v1_state_aliyun.puml: -------------------------------------------------------------------------------- 1 | 2 | @startuml 3 | 4 | skinparam class { 5 | BackgroundColor<> BurlyWood 6 | BackgroundColor<> BurlyWood 7 | BackgroundColor<> BurlyWood 8 | BackgroundColor<> BurlyWood 9 | BackgroundColor<> BurlyWood 10 | BackgroundColor<> BurlyWood 11 | BackgroundColor<> LightBlue 12 | BackgroundColor<> Crimson 13 | BackgroundColor<> Brown 14 | } 15 | 16 | class NumInput <> 17 | class Add2 <> 18 | class Add <> 19 | class Multi3 <> 20 | class Accumulate <> 21 | class LoadAccumulateValue <> 22 | class SaveAccumulateValue <> 23 | class AccumulateValueResource <> 24 | class Dispatch <> 25 | 26 | LoadAccumulateValue .. AccumulateValueResource 27 | SaveAccumulateValue .. AccumulateValueResource 28 | NumInput --> NI::Integer 29 | NI::Integer --> Dispatch 30 | Dispatch --> "1" DI::Integer 31 | Dispatch --> "2" DI::Unit 32 | DI::Unit --> LoadAccumulateValue 33 | LoadAccumulateValue --> LAL::Integer 34 | LAL::Integer --> "1" Accumulate 35 | DI::Integer --> Add2 36 | DI::Integer --> Multi3 37 | Add2 --> A2::Integer 38 | A2::Integer --> "2" Add 39 | Multi3 --> M3::Integer 40 | M3::Integer --> "1" Add 41 | Add --> A::Integer 42 | A::Integer --> "2" Accumulate 43 | Accumulate --> AC::Integer 44 | AC::Integer --> NumInput 45 | AC::Integer --> SaveAccumulateValue 46 | @enduml 47 | --------------------------------------------------------------------------------