├── .idea ├── misc.xml ├── modules.xml ├── servicetmpl1.iml ├── vcs.xml ├── workspace - 副本.xml └── workspace.xml ├── .vscode └── launch.json ├── LICENSE.txt ├── README.md ├── README.zh.md ├── app ├── app.go ├── config │ ├── appConfig.go │ ├── appConfigDev.yaml │ ├── appConfigProd.yaml │ └── configValidator.go ├── container │ ├── container.go │ ├── containerhelper │ │ └── serviceTmplContainer.go │ ├── dataservicefactory │ │ ├── cacheDataServiceFacory.go │ │ ├── dataServiceFactory.go │ │ ├── userDataServiceFactoryWrapper.go │ │ └── userdataservicefactory │ │ │ ├── couchdbUserDataServiceFactory.go │ │ │ ├── sqlUserDataServiceFactory.go │ │ │ └── userDataServiceFactory.go │ ├── datastorefactory │ │ ├── cacheGrpcFactory.go │ │ ├── couchdbFactory.go │ │ ├── datastoreFactory.go │ │ └── sqlFactory.go │ ├── servicecontainer │ │ └── serviceContainer.go │ └── usecasefactory │ │ ├── listUserFactory.go │ │ ├── registrationFactory.go │ │ ├── registrationTxFactory.go │ │ ├── useCaseFactory.go │ │ └── useCaseHelper.go └── logger │ └── logger.go ├── applicationservice ├── cacheclient │ ├── cacheClient.go │ └── generatedclient │ │ ├── cacheJin.pb.go │ │ └── doc.go ├── dataservice │ ├── dataService.go │ └── userdata │ │ ├── couchdb │ │ └── userDataCouchdb.go │ │ └── sqldb │ │ └── userDataSql.go ├── doc.go ├── paymentclient │ └── paymentClient.go └── userclient │ ├── generatedclient │ ├── usergrpc.pb.go │ └── usergrpc.proto │ └── userGrpc.go ├── cmd ├── grpcclient │ └── grpcClientMain.go ├── grpcserver │ └── grpcServerMain.go └── main.go ├── domain ├── model │ ├── cache.go │ ├── course.go │ └── user.go └── usecase │ ├── listuser │ └── listUser.go │ ├── registration │ ├── registrationHelper.go │ ├── registraton.go │ └── registratonTx.go │ └── useCase.go ├── go.mod ├── go.sum ├── script └── user.sql └── tool ├── doc.go └── timea └── timea.go /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/servicetmpl1.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/workspace - 副本.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 131 | 132 | 133 | 134 | jfeng 135 | AppConfig 136 | servicetmpl 137 | Gdbc 138 | userDataConfig 139 | registrationTx 140 | TxDataInterface 141 | logger2 142 | logger 143 | 2 144 | SQLConfig 145 | gdbc. 146 | servicetmpl1/tool/gdbc 147 | servicetmpl1/tool/txdataservice 148 | gdbc 149 | gtransaction 150 | initGdbc 151 | gtransaction/config 152 | logconfig 153 | EnableTx 154 | GetRegistrationUseCase 155 | 156 | 157 | servicetmpl1 158 | logger 159 | gtransation/gdbc 160 | gtransation/txdataservice 161 | 162 | 163 | D:\code\src\github.com\jfeng45\servicetmpl1 164 | 165 | 166 | 167 | 168 | 170 | 171 | 176 | 177 | 178 | 233 | 234 | 235 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 438 | 439 | true 440 | direct 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | 731 | 732 | 733 | 734 | 735 | 736 | 737 | 738 | 739 | 740 | 741 | 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | 751 | 752 | 753 | 754 | 755 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | 765 | 766 | 767 | 768 | 769 | 770 | 771 | 772 | 773 | 774 | 775 | 776 | 777 | 778 | 779 | 780 | 781 | 782 | 783 | 784 | 785 | 786 | 787 | 788 | 789 | 790 | 791 | 792 | 793 | 794 | 795 | 796 | 797 | 798 | 799 | 800 | 801 | 802 | 803 | 804 | 805 | 806 | 807 | 808 | 809 | 810 | 811 | 812 | 813 | 814 | 815 | 816 | 817 | 818 | 819 | 820 | 821 | 822 | 823 | 824 | 825 | 826 | 827 | 828 | 829 | 830 | 831 | 832 | 833 | 834 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch", 9 | "type": "go", 10 | "request": "launch", 11 | "mode": "auto", 12 | "program": "${workspaceFolder}/cmd/grpcserver/grpcServerMain.go", 13 | "env": {}, 14 | "args": [] 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Jin Feng 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A Self-Evolved Microservice Framework in Go 2 | 3 | Other language: 4 | ### **[中文](README.zh.md)** 5 | 6 | This is a major upgrade version of [jfeng45/servicetmpl](https://github.com/jfeng45/servicetmpl). 7 | 8 | The followings are a series of articles to explain the different areas of the application design: 9 | 10 | + [Go Microservice with Clean Architecture-A Major Upgrade]( 11 | https://medium.com/@jfeng45/go-microservice-with-clean-architecture-a-major-upgrade-34a4cedb0b06) 12 | + [A Self-Evolved Microservice Framework in Go](https://medium.com/@jfeng45/a-self-evolved-microservice-framework-in-go-d9bf87c10ab0) 13 | + [A Non-Intrusive Transaction Management Lib in Go — How to Use It](https://medium.com/swlh/a-non-intrusive-transaction-management-lib-in-go-how-to-use-it-a3e751cc1dd4) 14 | + [A Non-Intrusive Transaction Management Lib in Go — How it Works?](https://medium.com/nerd-for-tech/a-non-intrusive-transaction-management-lib-in-go-how-it-works-51d4b2ede8af) 15 | 16 | 17 | ## Getting Started 18 | 19 | ### Installation and Setting Up 20 | 21 | Don't need to finish all steps in this section up-front to get the code up running. The simplest way is to get the code from github and run it and come back to install the part when there is a real need. However, it will encounter an error when accesses the database. So, I'd recommend you install at least one database ( MySQL is better), then most of the code will work. 22 | 23 | #### Download Code 24 | 25 | ``` 26 | go get github.com/jfeng45/servicetmpl1 27 | ``` 28 | 29 | #### Set Up MySQL 30 | 31 | There are two database implementations, MySQL and CouchDB, but most functions are implemented in MySQL. You'd better install at least one of them. 32 | ``` 33 | Install MySQL 34 | run SQL script in script folder to create database and table 35 | ``` 36 | #### Install CouchDB 37 | 38 | The code works fine without it. CouchDB is created to show the feature of switching database by changing configuration. 39 | 40 | Installation on [Windows](https://docs.couchdb.org/en/2.2.0/install/windows.html) 41 | 42 | Installation on [Linux](https://docs.couchdb.org/en/2.2.0/install/unix.html) 43 | 44 | Installation on [Mac](https://docs.couchdb.org/en/2.2.0/install/mac.html) 45 | 46 | CouchDB [Example](https://github.com/go-kivik/kivik/wiki/Usage-Examples) 47 | 48 | #### Set up CouchDB 49 | 50 | ``` 51 | Access "Fauxton" through browser: http://localhost:5984/_utils/# (login with: admin/admin). 52 | Create new database "service_config" in "Fauxton". 53 | Add the following document to the database ( "_id" and "_rev" are generated by database, no need to change it): 54 | { 55 | "_id": "80a9134c7dfa53f67f6be214e1000fa7", 56 | "_rev": "4-f45fb8bdd454a71e6ae88bdeea8a0b4c", 57 | "uid": 10, 58 | "username": "Tony", 59 | "department": "IT", 60 | "created": "2018-02-17T15:04:05-03:00" 61 | } 62 | ``` 63 | #### Install Cache Service (Another Microservice) 64 | 65 | Without it, calling another Microservice piece won't work, the rest of application works fine. Please follow instructions in [reservegrpc](https://github.com/jfeng45/reservegrpc) to set up the service. 66 | 67 | ### Start Application 68 | 69 | #### Start MySQL Server 70 | ``` 71 | cd [MySQLroot]/bin 72 | mysqld 73 | ``` 74 | 75 | #### Start CouchDB Server 76 | ``` 77 | It should already have been started 78 | ``` 79 | #### Start Cache Service 80 | 81 | Please follow instructions in [reservegrpc](https://github.com/jfeng45/reservegrpc) to start the server. 82 | 83 | #### Run main 84 | 85 | ##### Run as a local application 86 | In "main()" function of "main.go", there are two functions "testMySql()" and "testCouchDB()". 87 | "testMySql()" reads configurations from "configs/appConifgDev.yaml" and accesses MySQL. "testCouchDB()" reads from "configs/appConifgProd.yaml" and access CouchDB. 88 | There are multiple functions in "testMySql()", you can focus on testing one each time by commenting out others. 89 | ``` 90 | cd [rootOfProject]/cmd 91 | go run main.go 92 | ``` 93 | ##### Run as a gRPC Microservice application 94 | 95 | Start gRPC Server 96 | ``` 97 | cd [rootOfProject]/cmd/grpcserver 98 | go run grpcServerMain.go 99 | ``` 100 | Start gRPC Client 101 | ``` 102 | cd [rootOfProject]/cmd/grpcclient 103 | go run grpcClientMain.go 104 | ``` 105 | 106 | ## License 107 | 108 | [MIT](LICENSE.txt) License 109 | 110 | 111 | -------------------------------------------------------------------------------- /README.zh.md: -------------------------------------------------------------------------------- 1 | ## 一个能自我进化的Go微服务框架 2 | 3 | 其他语言: 4 | 5 | ### **[English](README.md)** 6 | 7 | 这是一个Go微服务框架。它是对[jfeng45/servicetmpl](https://github.com/jfeng45/servicetmpl1)的一个重大升级版。 8 | 9 | 下面是描述它的系列文章: 10 | 11 | + [一个能自我进化的Go微服务框架](https://blog.csdn.net/weixin_38748858/article/details/106996260) 12 | 13 | + [一个非侵入的Go事务管理库--怎样使用](https://blog.csdn.net/weixin_38748858/article/details/106885990) 14 | 15 | + [一个非侵入的Go事务管理库--工作原理](https://blog.csdn.net/weixin_38748858/article/details/106886184) 16 | 17 | + [清晰架构(Clean Architecture)的Go微服务--重大升级](https://blog.csdn.net/weixin_38748858/article/details/107565358) 18 | 19 | ## 运行 20 | 21 | ### 安装和设置 22 | 23 | 不需要完成本节中的所有步骤以使代码运行。 最简单的方法是从github获取代码并运行它,然后在真正需要某些部件时再返回安装。 但是,访问数据库时会遇到错误。 24 | 所以,我建议你至少安装一个数据库(MySQL更好),然后大部分代码就都可以运行了。 25 | 26 | #### 下载程序 27 | 28 | ``` 29 | go get github.com/jfeng45/servicetmpl1 30 | ``` 31 | 32 | #### 设置MySQL 33 | 34 | 有两个数据库实现,MySQL和CouchDB,但大多数函数都是在MySQL中实现的。 你最好安装至少其中一个。 35 | 36 | ``` 37 | 安装MySQL 38 | 在script文件夹中运行SQL脚本以创建数据库和表 39 | ``` 40 | #### 安装CouchDB 41 | 42 | 没有它,代码工作正常。创建CouchDB用来完成切换数据库的功能(通过更改配置)。 43 | 44 | 安装[Windows](https://docs.couchdb.org/en/2.2.0/install/windows.html) 45 | 46 | 安装[Linux](https://docs.couchdb.org/en/2.2.0/install/unix.html) 47 | 48 | 安装[Mac](https://docs.couchdb.org/en/2.2.0/install/mac.html) 49 | 50 | CouchDB[Example](https://github.com/go-kivik/kivik/wiki/Usage-Examples) 51 | 52 | #### 设置CouchDB 53 | 54 | ``` 55 | 通过浏览器访问“Fauxton”:http://localhost:5984/_utils/#(使用:admin/admin登录)。 56 | 在“Fauxton”中创建新数据库“service_config”。 57 | 将以下文档添加到数据库(“_id”和“_rev”由数据库生成,无需更改): 58 | { 59 | "_id": "80a9134c7dfa53f67f6be214e1000fa7", 60 | "_rev": "4-f45fb8bdd454a71e6ae88bdeea8a0b4c", 61 | "uid": 10, 62 | "username": "Tony", 63 | "department": "IT", 64 | "created": "2018-02-17T15:04:05-03:00" 65 | } 66 | ``` 67 | #### 安装缓存服务(另一个微服务) 68 | 69 | 没有它,调用另一个微服务部分将无法正常工作,其余部分工作正常。请按照[reservegrpc](https://github.com/jfeng45/reservegrpc)中的说明设置服务。 70 | 71 | ### 启动应用程序 72 | 73 | #### 启动MySQL 74 | ``` 75 | cd [MySQLroot]/bin 76 | mysqld 77 | ``` 78 | 79 | #### 启动CouchDB 80 | ``` 81 | 它应该已经启动了 82 | ``` 83 | #### 启动缓存服务 84 | 85 | 请按照[reservegrpc](https://github.com/jfeng45/reservegrpc)中的说明启动服务器。 86 | 87 | #### 运行main 88 | 89 | ##### 作为本地应用程序运行 90 | 91 | 在“main.go”的“main()”函数中,有两个函数“testMySql()”和“testCouchDB()”。 92 | “testMySql()”从“configs/appConifgDev.yaml”读取配置并访问MySQL。 “testCouchDB()”从“configs/appConifgProd.yaml”读取配置并访问CouchDB。 93 | “testMySql()”中有多个函数,你可以通过注释掉其他函数来单独测试一个函数。 94 | 95 | ``` 96 | cd [rootOfProject]/cmd 97 | go run main.go 98 | ``` 99 | ##### 作为gRPC微服务应用程序运行 100 | 101 | 启动gRPC服务器 102 | ``` 103 | cd [rootOfProject]/cmd/grpcserver 104 | go run grpcServerMain.go 105 | ``` 106 | 启动gRPC客户端 107 | ``` 108 | cd [rootOfProject]/cmd/grpcclient 109 | go run grpcClientMain.go 110 | ``` 111 | 112 | ### 授权 113 | 114 | [MIT](LICENSE.txt) 授权 115 | 116 | 117 | -------------------------------------------------------------------------------- /app/app.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | logConfig "github.com/jfeng45/glogger/config" 5 | logFactory "github.com/jfeng45/glogger/factory" 6 | "github.com/jfeng45/servicetmpl1/app/config" 7 | "github.com/jfeng45/servicetmpl1/app/container" 8 | "github.com/jfeng45/servicetmpl1/app/container/servicecontainer" 9 | "github.com/jfeng45/servicetmpl1/app/logger" 10 | "github.com/pkg/errors" 11 | ) 12 | 13 | // InitApp loads the application configurations from a file and saved it in appConfig and initialize the logger 14 | // The appConfig is cached in container, so it only loads the configuration file once. 15 | // InitApp only needs to be called once. If the configuration changes, you can call it again to reinitialize the app. 16 | func InitApp(filename...string) (container.Container, error) { 17 | config, err := config.BuildConfig(filename...) 18 | if err != nil { 19 | return nil, errors.Wrap(err, "BuildConfig") 20 | } 21 | err = initLogger(&config.LogConfig) 22 | if err != nil { 23 | return nil, err 24 | } 25 | return initContainer(config) 26 | } 27 | 28 | func initLogger (lc *logConfig.Logging) error{ 29 | log, err := logFactory.Build(lc) 30 | if err != nil { 31 | return errors.Wrap(err, "loadLogger") 32 | } 33 | logger.SetLogger(log) 34 | return nil 35 | } 36 | 37 | func initContainer(config *config.AppConfig) (container.Container, error) { 38 | factoryMap := make(map[string]interface{}) 39 | c := servicecontainer.ServiceContainer{factoryMap,config} 40 | //gdbc, err :=initGdbc(&c.AppConfig.SQLConfig) 41 | //if err != nil { 42 | // return nil,err 43 | //} 44 | //key := config.SQLConfig.Code 45 | //c.Put(key, gdbc) 46 | return &c, nil 47 | } 48 | -------------------------------------------------------------------------------- /app/config/appConfig.go: -------------------------------------------------------------------------------- 1 | // Package config reasd configurations from a YAML file and load them into a AppConfig type to save the configuration 2 | // information for the application. 3 | // Configuration for different environment can be saved in files with different suffix, for example [Dev], [Prod] 4 | package config 5 | 6 | import ( 7 | "fmt" 8 | logConfig "github.com/jfeng45/glogger/config" 9 | "github.com/pkg/errors" 10 | "gopkg.in/yaml.v2" 11 | "io/ioutil" 12 | ) 13 | 14 | // AppConfig represents the application config 15 | type AppConfig struct { 16 | SQLConfig DataStoreConfig `yaml:"sqlConfig"` 17 | SQLConfigTx DataStoreConfig `yaml:"sqlConfigTx"` 18 | CouchdbConfig DataStoreConfig `yaml:"couchdbConfig"` 19 | CacheGrpcConfig DataStoreConfig `yaml:"cacheGrpcConfig"` 20 | UserGrpcConfig DataStoreConfig `yaml:"userGrpcConfig"` 21 | ZapConfig logConfig.Logging `yaml:"zapConfig"` 22 | LorusConfig logConfig.Logging `yaml:"logrusConfig"` 23 | LogConfig logConfig.Logging `yaml:"logConfig"` 24 | UseCaseConfig UseCaseConfig `yaml:"useCaseConfig"` 25 | } 26 | 27 | // UseCaseConfig represents different use cases 28 | type UseCaseConfig struct { 29 | Registration RegistrationConfig `yaml:"registration"` 30 | RegistrationTx RegistrationTxConfig `yaml:"registrationTx"` 31 | ListUser ListUserConfig `yaml:"listUser"` 32 | } 33 | 34 | // RegistrationConfig represents registration use case 35 | type RegistrationConfig struct { 36 | Code string `yaml:"code"` 37 | UserDataConfig DataConfig `yaml:"userDataConfig"` 38 | } 39 | 40 | // RegistrationConfigTx represents registration use cases that support transaction 41 | type RegistrationTxConfig struct { 42 | Code string `yaml:"code"` 43 | UserDataConfig DataConfig `yaml:"userDataConfig"` 44 | } 45 | 46 | // ListUserConfig represents list user use case 47 | type ListUserConfig struct { 48 | Code string `yaml:"code"` 49 | UserDataConfig DataConfig `yaml:"userDataConfig"` 50 | CacheDataConfig DataConfig `yaml:"cacheDataConfig"` 51 | } 52 | 53 | // DataConfig represents data service 54 | type DataConfig struct { 55 | Code string `yaml:"code"` 56 | DataStoreConfig DataStoreConfig `yaml:"dataStoreConfig"` 57 | } 58 | 59 | // DataConfig represents handlers for data store. It can be a database or a gRPC connection 60 | type DataStoreConfig struct { 61 | Code string `yaml:"code"` 62 | // Only database has a driver name, for grpc it is "tcp" ( network) for server 63 | DriverName string `yaml:"driverName"` 64 | // For database, this is datasource name; for grpc, it is target url 65 | UrlAddress string `yaml:"urlAddress"` 66 | // Only some databases need this database name 67 | DbName string `yaml:"dbName"` 68 | // To indicate whether support transaction or not. "true" means supporting transaction 69 | Tx bool `yaml:"tx"` 70 | } 71 | 72 | // LogConfig represents logger handler 73 | // Logger has many parameters can be set or changed. Currently, only three are listed here. Can add more into it to 74 | // fits your needs. 75 | type LogConfig struct { 76 | // log library name 77 | Code string `yaml:"code"` 78 | // log level 79 | Level string `yaml:"level"` 80 | // show caller in log message 81 | EnableCaller bool `yaml:"enableCaller"` 82 | } 83 | 84 | // BuildConfig build the AppConfig 85 | // if the filaname is not empty, then it reads the file of the filename (in the same folder) and put it into the AppConfig 86 | func BuildConfig(filename ...string) (*AppConfig, error) { 87 | if len(filename) == 1 { 88 | return buildConfigFromFile(filename[0]) 89 | } else { 90 | return BuildConfigWithoutFile() 91 | } 92 | } 93 | 94 | // BuildConfigWithoutFile create AppConfig with adhoc value 95 | func BuildConfigWithoutFile() (*AppConfig, error) { 96 | return nil, nil 97 | } 98 | 99 | // buildConfigFromFile reads the file of the filename (in the same folder) and put it into the AppConfig 100 | func buildConfigFromFile(filename string) (*AppConfig, error) { 101 | 102 | var ac AppConfig 103 | file, err := ioutil.ReadFile(filename) 104 | if err != nil { 105 | return nil, errors.Wrap(err, "read error") 106 | } 107 | err = yaml.Unmarshal(file, &ac) 108 | 109 | if err != nil { 110 | return nil, errors.Wrap(err, "unmarshal") 111 | } 112 | err = validateConfig(ac) 113 | if err != nil { 114 | return nil, errors.Wrap(err, "validate config") 115 | } 116 | fmt.Println("appConfig:", ac) 117 | return &ac, nil 118 | } 119 | 120 | 121 | -------------------------------------------------------------------------------- /app/config/appConfigDev.yaml: -------------------------------------------------------------------------------- 1 | sqlConfig: &sqlConfig 2 | code: sqldb 3 | driverName: mysql 4 | urlAddress: "root:@tcp(localhost:4333)/service_config?charset=utf8" 5 | dbName: 6 | tx: false 7 | sqlConfigTx: &sqlConfigTx 8 | code: sqldb 9 | driverName: mysql 10 | urlAddress: "root:@tcp(localhost:4333)/service_config?charset=utf8" 11 | dbName: 12 | tx: true 13 | couchdbConfig: &couchdbConfig 14 | code: couch 15 | driverName: couch 16 | urlAddress: http://admin:admin@localhost:5984 17 | dbName: service_config 18 | tx: false 19 | cacheGrpcConfig: &cacheGrpcConfig 20 | code: cacheGrpc 21 | driverName: tcp 22 | urlAddress: localhost:5051 23 | userGrpcConfig: &userGrpcConfig 24 | code: userGrpc 25 | driverName: tcp 26 | urlAddress: localhost:5052 27 | zapConfig: &zapConfig 28 | code: zap 29 | level: debug 30 | enableCaller: true 31 | logrusConfig: &logrusConfig 32 | code: logrus 33 | level: debug 34 | enableCaller: false 35 | logConfig: *zapConfig 36 | useCaseConfig: 37 | registration: 38 | code: registration 39 | userDataConfig: &userDataConfig 40 | code: userData 41 | dataStoreConfig: *sqlConfig 42 | listUser: 43 | code: listUser 44 | userDataConfig: *userDataConfig 45 | cacheDataConfig: &cacheDataConfig 46 | code: cacheData 47 | dataStoreConfig: *cacheGrpcConfig 48 | registrationTx: 49 | code: registrationTx 50 | userDataConfig: &userDataConfigTx 51 | code: userData 52 | dataStoreConfig: *sqlConfigTx 53 | 54 | 55 | -------------------------------------------------------------------------------- /app/config/appConfigProd.yaml: -------------------------------------------------------------------------------- 1 | sqlConfig: &sqlConfig 2 | code: sqldb 3 | driverName: mysql 4 | urlAddress: "root:@tcp(localhost:4333)/service_config?charset=utf8" 5 | dbName: 6 | tx: false 7 | sqlConfigTx: &sqlConfigTx 8 | code: sqldb 9 | driverName: mysql 10 | urlAddress: "root:@tcp(localhost:4333)/service_config?charset=utf8" 11 | dbName: 12 | tx: true 13 | couchdbConfig: &couchdbConfig 14 | code: couch 15 | driverName: couch 16 | urlAddress: http://admin:admin@localhost:5984 17 | dbName: service_config 18 | tx: false 19 | cacheGrpcConfig: &cacheGrpcConfig 20 | code: cacheGrpc 21 | driverName: cache 22 | urlAddress: localhost:5051 23 | userGrpcConfig: &userGrpcConfig 24 | code: userGrpc 25 | driverName: tcp 26 | urlAddress: localhost:5052 27 | zapConfig: &zapConfig 28 | code: zap 29 | level: debug 30 | enableCaller: true 31 | logrusConfig: &logrusConfig 32 | code: logrus 33 | level: debug 34 | enableCaller: false 35 | logConfig: *zapConfig 36 | useCaseConfig: 37 | registration: 38 | code: registration 39 | userDataConfig: &userDataConfig 40 | code: userData 41 | dataStoreConfig: *couchdbConfig 42 | listUser: 43 | code: listUser 44 | userDataConfig: *userDataConfig 45 | cacheDataConfig: &cacheDataConfig 46 | code: cacheData 47 | dataStoreConfig: *cacheGrpcConfig 48 | registrationTx: 49 | code: registrationTx 50 | userDataConfig: 51 | code: userData 52 | dataStoreConfig: *couchdbConfig -------------------------------------------------------------------------------- /app/config/configValidator.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | ) 6 | 7 | // database code. Need to map to the database code (DataStoreConfig) in the configuration yaml file. 8 | const ( 9 | SQLDB string = "sqldb" 10 | COUCHDB string = "couch" 11 | CACHE_GRPC string = "cacheGrpc" 12 | USER_GRPC string = "userGrpc" 13 | ) 14 | 15 | // constant for logger code, it needs to match log code (logConfig)in configuration 16 | const ( 17 | LOGRUS string = "logrus" 18 | ZAP string = "zap" 19 | ) 20 | 21 | // use case code. Need to map to the use case code (UseCaseConfig) in the configuration yaml file. 22 | // Client app use those to retrieve use case from the container 23 | const ( 24 | REGISTRATION string = "registration" 25 | REGISTRATION_TX string = "registrationTx" 26 | LIST_USER string = "listUser" 27 | ) 28 | 29 | // data service code. Need to map to the data service code (DataConfig) in the configuration yaml file. 30 | const ( 31 | USER_DATA string = "userData" 32 | CACHE_DATA string = "cacheData" 33 | TX_DATA string = "txData" 34 | //COURSE_DATA string = "courseData" 35 | ) 36 | 37 | func validateConfig(appConfig AppConfig) error { 38 | err := validateDataStore(appConfig) 39 | if err != nil { 40 | return errors.Wrap(err, "") 41 | } 42 | err = validateLogger(appConfig) 43 | if err != nil { 44 | return errors.Wrap(err, "") 45 | } 46 | useCase := appConfig.UseCaseConfig 47 | err = validateUseCase(useCase) 48 | if err != nil { 49 | return errors.Wrap(err, "") 50 | } 51 | return nil 52 | } 53 | 54 | func validateLogger(appConfig AppConfig) error { 55 | zc := appConfig.ZapConfig 56 | key := zc.Code 57 | zcMsg := " in validateLogger doesn't match key = " 58 | if ZAP != key { 59 | errMsg := ZAP + zcMsg + key 60 | return errors.New(errMsg) 61 | } 62 | lc := appConfig.LorusConfig 63 | key = lc.Code 64 | if LOGRUS != lc.Code { 65 | errMsg := LOGRUS + zcMsg + key 66 | return errors.New(errMsg) 67 | } 68 | return nil 69 | } 70 | 71 | func validateDataStore(appConfig AppConfig) error { 72 | sc := appConfig.SQLConfig 73 | key := sc.Code 74 | scMsg := " in validateDataStore doesn't match key = " 75 | if SQLDB != key { 76 | errMsg := SQLDB + scMsg + key 77 | return errors.New(errMsg) 78 | } 79 | cc := appConfig.CouchdbConfig 80 | key = cc.Code 81 | if COUCHDB != key { 82 | errMsg := COUCHDB + scMsg + key 83 | return errors.New(errMsg) 84 | } 85 | cgc := appConfig.CacheGrpcConfig 86 | key = cgc.Code 87 | if CACHE_GRPC != key { 88 | errMsg := CACHE_GRPC + scMsg + key 89 | return errors.New(errMsg) 90 | } 91 | 92 | ugc := appConfig.UserGrpcConfig 93 | key = ugc.Code 94 | if USER_GRPC != key { 95 | errMsg := USER_GRPC + scMsg + key 96 | return errors.New(errMsg) 97 | } 98 | 99 | return nil 100 | } 101 | 102 | func validateUseCase(useCase UseCaseConfig) error { 103 | err := validateRegistration(useCase) 104 | if err != nil { 105 | return errors.Wrap(err, "") 106 | } 107 | err = validateRegistrationTx(useCase) 108 | if err != nil { 109 | return errors.Wrap(err, "") 110 | } 111 | err = validateListUser(useCase) 112 | if err != nil { 113 | return errors.Wrap(err, "") 114 | } 115 | return nil 116 | } 117 | 118 | func validateRegistration(useCaseConfig UseCaseConfig) error { 119 | rc := useCaseConfig.Registration 120 | key := rc.Code 121 | rcMsg := " in validateRegistration doesn't match key = " 122 | if REGISTRATION != key { 123 | errMsg := REGISTRATION + rcMsg + key 124 | return errors.New(errMsg) 125 | } 126 | key = rc.UserDataConfig.Code 127 | if USER_DATA != key { 128 | errMsg := USER_DATA + rcMsg + key 129 | return errors.New(errMsg) 130 | } 131 | return nil 132 | } 133 | 134 | func validateRegistrationTx(useCaseConfig UseCaseConfig) error { 135 | rc := useCaseConfig.RegistrationTx 136 | key := rc.Code 137 | rcMsg := " in validateRegistrationTx doesn't match key = " 138 | if REGISTRATION_TX != key { 139 | errMsg := REGISTRATION_TX + rcMsg + key 140 | return errors.New(errMsg) 141 | } 142 | key = rc.UserDataConfig.Code 143 | if USER_DATA != key { 144 | errMsg := USER_DATA + rcMsg + key 145 | return errors.New(errMsg) 146 | } 147 | return nil 148 | } 149 | 150 | func validateListUser(useCaseConfig UseCaseConfig) error { 151 | lc := useCaseConfig.ListUser 152 | key := lc.Code 153 | luMsg := " in validateListUser doesn't match key = " 154 | if LIST_USER != key { 155 | errMsg := LIST_USER + luMsg + key 156 | return errors.New(errMsg) 157 | } 158 | key = lc.CacheDataConfig.Code 159 | if CACHE_DATA != key { 160 | errMsg := CACHE_DATA + luMsg + key 161 | return errors.New(errMsg) 162 | } 163 | return nil 164 | } 165 | 166 | -------------------------------------------------------------------------------- /app/container/container.go: -------------------------------------------------------------------------------- 1 | // package container use dependency injection to create concrete type and wire the whole application together 2 | package container 3 | 4 | type Container interface { 5 | // BuildUseCase creates concrete types for use case and it is included types. 6 | // For each call, it will create a new instance, which means it is not a singleton 7 | // Only exceptions are data store handlers, which are singletons. They are cached in container. 8 | BuildUseCase(code string) (interface{}, error) 9 | 10 | // This should only be used by container and it's sub-package 11 | // Get instance by code from container. Only data store handler can be retrieved from container 12 | Get(code string) (interface{}, bool) 13 | 14 | // This should only be used by container and it's sub-package 15 | // Put value into container with code as the key. Only data store handler is saved in container 16 | Put(code string, value interface{}) 17 | } 18 | -------------------------------------------------------------------------------- /app/container/containerhelper/serviceTmplContainer.go: -------------------------------------------------------------------------------- 1 | package containerhelper 2 | 3 | import ( 4 | "github.com/jfeng45/servicetmpl1/app/config" 5 | "github.com/jfeng45/servicetmpl1/app/container" 6 | "github.com/jfeng45/servicetmpl1/domain/usecase" 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | func GetListUserUseCase(c container.Container) (usecase.ListUserUseCaseInterface, error) { 11 | key := config.LIST_USER 12 | value, err := c.BuildUseCase(key) 13 | if err != nil { 14 | //logger.Log.Errorf("%+v\n", err) 15 | return nil, errors.Wrap(err, "") 16 | } 17 | return value.(usecase.ListUserUseCaseInterface), nil 18 | } 19 | 20 | func GetRegistrationUseCase(c container.Container) (usecase.RegistrationUseCaseInterface, error) { 21 | key := config.REGISTRATION 22 | value, err := c.BuildUseCase(key) 23 | if err != nil { 24 | //logger.Log.Errorf("%+v\n", err) 25 | return nil, errors.Wrap(err, "") 26 | } 27 | return value.(usecase.RegistrationUseCaseInterface), nil 28 | 29 | } 30 | 31 | func GetRegistrationTxUseCase(c container.Container) (usecase.RegistrationTxUseCaseInterface, error) { 32 | key := config.REGISTRATION_TX 33 | value, err := c.BuildUseCase(key) 34 | if err != nil { 35 | //logger.Log.Errorf("%+v\n", err) 36 | return nil, errors.Wrap(err, "") 37 | } 38 | return value.(usecase.RegistrationTxUseCaseInterface), nil 39 | 40 | } 41 | -------------------------------------------------------------------------------- /app/container/dataservicefactory/cacheDataServiceFacory.go: -------------------------------------------------------------------------------- 1 | package dataservicefactory 2 | 3 | import ( 4 | "github.com/jfeng45/servicetmpl1/app/config" 5 | "github.com/jfeng45/servicetmpl1/app/container" 6 | "github.com/jfeng45/servicetmpl1/app/container/datastorefactory" 7 | "github.com/jfeng45/servicetmpl1/app/logger" 8 | "github.com/jfeng45/servicetmpl1/applicationservice/cacheclient" 9 | "github.com/pkg/errors" 10 | "google.golang.org/grpc" 11 | ) 12 | 13 | // cacheDataServiceFactory is a empty receiver for Build method 14 | type cacheDataServiceFactory struct{} 15 | 16 | func (cdsf *cacheDataServiceFactory) Build(c container.Container, dataConfig *config.DataConfig) (DataServiceInterface, error) { 17 | logger.Log.Debug("cacheDataServiceFactory") 18 | dsc := dataConfig.DataStoreConfig 19 | dsi, err := datastorefactory.GetDataStoreFb(dsc.Code).Build(c, &dsc) 20 | grpcConn := dsi.(*grpc.ClientConn) 21 | if err != nil { 22 | return nil, errors.Wrap(err, "") 23 | } 24 | cdg := cacheclient.CacheDataGrpc{grpcConn} 25 | //logger.Log.Debug("udm:", udm.DB) 26 | 27 | return &cdg, nil 28 | } 29 | -------------------------------------------------------------------------------- /app/container/dataservicefactory/dataServiceFactory.go: -------------------------------------------------------------------------------- 1 | // Package dataservicefactory using factory method pattern to create concrete type to provide persistence service 2 | // The source of data can come from database ( for domain model "user") or from other service ( for domain model cache, 3 | // which comes from a gRPC service). 4 | // There is only one method Build() for the factory and all different types of data service following the same interface 5 | // to build the data service. 6 | 7 | package dataservicefactory 8 | 9 | import ( 10 | "github.com/jfeng45/servicetmpl1/app/config" 11 | "github.com/jfeng45/servicetmpl1/app/container" 12 | ) 13 | 14 | // To map "data service code" to "data service interface builder" 15 | // Each data service need a separate builder 16 | // Concrete builder is in corresponding factory file. For example, "courseDataServiceFactory" is in 17 | // "courseDataServiceFactory.go" 18 | var dsFbMap = map[string]dataServiceFbInterface{ 19 | config.USER_DATA: &userDataServiceFactoryWrapper{}, 20 | config.CACHE_DATA: &cacheDataServiceFactory{}, 21 | } 22 | 23 | // DataServiceInterface serves as a marker to indicate the return type for Build method 24 | type DataServiceInterface interface{} 25 | 26 | // The builder interface for factory method pattern 27 | // Every factory needs to implement Build method 28 | type dataServiceFbInterface interface { 29 | Build(container.Container, *config.DataConfig) (DataServiceInterface, error) 30 | } 31 | 32 | // GetDataServiceFb is accessors for factoryBuilderMap 33 | func GetDataServiceFb(key string) dataServiceFbInterface { 34 | return dsFbMap[key] 35 | } 36 | -------------------------------------------------------------------------------- /app/container/dataservicefactory/userDataServiceFactoryWrapper.go: -------------------------------------------------------------------------------- 1 | package dataservicefactory 2 | 3 | import ( 4 | "github.com/jfeng45/servicetmpl1/app/config" 5 | "github.com/jfeng45/servicetmpl1/app/container" 6 | "github.com/jfeng45/servicetmpl1/app/container/dataservicefactory/userdataservicefactory" 7 | "github.com/jfeng45/servicetmpl1/app/logger" 8 | "github.com/pkg/errors" 9 | ) 10 | 11 | // userDataServiceFactory is a empty receiver for Build method 12 | type userDataServiceFactoryWrapper struct{} 13 | 14 | func (udsfw *userDataServiceFactoryWrapper) Build(c container.Container, dataConfig *config.DataConfig) (DataServiceInterface, error) { 15 | logger.Log.Debug("UserDataServiceFactory") 16 | key := dataConfig.DataStoreConfig.Code 17 | udsi, err := userdataservicefactory.GetUserDataServiceFb(key).Build(c, dataConfig) 18 | if err != nil { 19 | return nil, errors.Wrap(err, "") 20 | } 21 | return udsi, nil 22 | } 23 | 24 | -------------------------------------------------------------------------------- /app/container/dataservicefactory/userdataservicefactory/couchdbUserDataServiceFactory.go: -------------------------------------------------------------------------------- 1 | package userdataservicefactory 2 | 3 | import ( 4 | "github.com/go-kivik/kivik" 5 | "github.com/jfeng45/servicetmpl1/app/config" 6 | "github.com/jfeng45/servicetmpl1/app/container" 7 | "github.com/jfeng45/servicetmpl1/app/container/datastorefactory" 8 | "github.com/jfeng45/servicetmpl1/app/logger" 9 | "github.com/jfeng45/servicetmpl1/applicationservice/dataservice" 10 | "github.com/jfeng45/servicetmpl1/applicationservice/dataservice/userdata/couchdb" 11 | "github.com/pkg/errors" 12 | ) 13 | 14 | // couchdbUserDataServiceFactory is a empty receiver for Build method 15 | type couchdbUserDataServiceFactory struct{} 16 | 17 | func (cudsf *couchdbUserDataServiceFactory) Build(c container.Container, dataConfig *config.DataConfig) (dataservice.UserDataInterface, error) { 18 | logger.Log.Debug("couchdbUserDataServiceFactory") 19 | dsc := dataConfig.DataStoreConfig 20 | dsi, err := datastorefactory.GetDataStoreFb(dsc.Code).Build(c, &dsc) 21 | if err != nil { 22 | return nil, errors.Wrap(err, "") 23 | } 24 | ds := dsi.(*kivik.DB) 25 | udc := couchdb.UserDataCouchdb{DB: ds} 26 | return &udc, nil 27 | 28 | } 29 | -------------------------------------------------------------------------------- /app/container/dataservicefactory/userdataservicefactory/sqlUserDataServiceFactory.go: -------------------------------------------------------------------------------- 1 | package userdataservicefactory 2 | 3 | import ( 4 | "github.com/jfeng45/gtransaction/gdbc" 5 | "github.com/jfeng45/servicetmpl1/app/config" 6 | "github.com/jfeng45/servicetmpl1/app/container" 7 | "github.com/jfeng45/servicetmpl1/app/container/datastorefactory" 8 | "github.com/jfeng45/servicetmpl1/app/logger" 9 | "github.com/jfeng45/servicetmpl1/applicationservice/dataservice" 10 | "github.com/jfeng45/servicetmpl1/applicationservice/dataservice/userdata/sqldb" 11 | "github.com/pkg/errors" 12 | ) 13 | 14 | // sqlUserDataServiceFactory is a empty receiver for Build method 15 | type sqlUserDataServiceFactory struct{} 16 | 17 | func (sudsf *sqlUserDataServiceFactory) Build(c container.Container, dataConfig *config.DataConfig) (dataservice.UserDataInterface, error) { 18 | logger.Log.Debug("sqlUserDataServiceFactory") 19 | dsc := dataConfig.DataStoreConfig 20 | dsi, err := datastorefactory.GetDataStoreFb(dsc.Code).Build(c, &dsc) 21 | if err != nil { 22 | return nil, errors.Wrap(err, "") 23 | } 24 | ds := dsi.(gdbc.SqlGdbc) 25 | uds := sqldb.UserDataSql{DB: ds} 26 | logger.Log.Debug("uds:", uds.DB) 27 | return &uds, nil 28 | 29 | } 30 | -------------------------------------------------------------------------------- /app/container/dataservicefactory/userdataservicefactory/userDataServiceFactory.go: -------------------------------------------------------------------------------- 1 | package userdataservicefactory 2 | 3 | import ( 4 | "github.com/jfeng45/servicetmpl1/app/config" 5 | "github.com/jfeng45/servicetmpl1/app/container" 6 | "github.com/jfeng45/servicetmpl1/applicationservice/dataservice" 7 | ) 8 | 9 | var udsFbMap = map[string]userDataServiceFbInterface{ 10 | config.SQLDB: &sqlUserDataServiceFactory{}, 11 | config.COUCHDB: &couchdbUserDataServiceFactory{}, 12 | } 13 | 14 | // The builder interface for factory method pattern 15 | // Every factory needs to implement Build method 16 | type userDataServiceFbInterface interface { 17 | Build(container.Container, *config.DataConfig) (dataservice.UserDataInterface, error) 18 | } 19 | 20 | // GetDataServiceFb is accessors for factoryBuilderMap 21 | func GetUserDataServiceFb(key string) userDataServiceFbInterface { 22 | return udsFbMap[key] 23 | } 24 | -------------------------------------------------------------------------------- /app/container/datastorefactory/cacheGrpcFactory.go: -------------------------------------------------------------------------------- 1 | package datastorefactory 2 | 3 | import ( 4 | "github.com/jfeng45/servicetmpl1/app/config" 5 | "github.com/jfeng45/servicetmpl1/app/container" 6 | "github.com/jfeng45/servicetmpl1/app/logger" 7 | "github.com/pkg/errors" 8 | "google.golang.org/grpc" 9 | ) 10 | 11 | // cacheGrpcFactory is an empty receiver for Build method 12 | type cacheGrpcFactory struct{} 13 | 14 | func (cgf *cacheGrpcFactory) Build(c container.Container, dsc *config.DataStoreConfig) (DataStoreInterface, error) { 15 | key := dsc.Code 16 | //if it is already in container, return 17 | if value, found := c.Get(key); found { 18 | logger.Log.Debug("find CacheGrpc key=%v \n", key) 19 | return value.(*grpc.ClientConn), nil 20 | } 21 | //not in map, need to create one 22 | logger.Log.Debug("doesn't find cacheGrpc key=%v need to created a new one\n", key) 23 | 24 | conn, err := grpc.Dial(dsc.UrlAddress, grpc.WithInsecure()) 25 | if err != nil { 26 | return nil, errors.Wrap(err, "") 27 | } 28 | c.Put(key, conn) 29 | return conn, err 30 | } 31 | -------------------------------------------------------------------------------- /app/container/datastorefactory/couchdbFactory.go: -------------------------------------------------------------------------------- 1 | package datastorefactory 2 | 3 | import ( 4 | "context" 5 | couchdbKivid "github.com/go-kivik/couchdb" 6 | "github.com/go-kivik/kivik" 7 | "github.com/jfeng45/servicetmpl1/app/container" 8 | "github.com/jfeng45/servicetmpl1/app/logger" 9 | 10 | //"github.com/flimzy/kivik" 11 | "github.com/jfeng45/servicetmpl1/app/config" 12 | "github.com/pkg/errors" 13 | ) 14 | 15 | // couchdbFactory is receiver for Build method 16 | type couchdbFactory struct{} 17 | 18 | // implement Build method for CouchDB database 19 | func (cf *couchdbFactory) Build(c container.Container, dsc *config.DataStoreConfig) (DataStoreInterface, error) { 20 | logger.Log.Debug("couchdbFactory") 21 | key := dsc.Code 22 | 23 | //if it is already in container, return 24 | if value, found := c.Get(key); found { 25 | logger.Log.Debug("found couchdb in container for key:", key) 26 | return value.(*kivik.DB), nil 27 | } 28 | // Don't know why needs adding the following line, because the driver is already registered in init() in couchdbKiv 29 | // however, not adding this, I got the error "unknown driver "couch" (forgotten import?)" 30 | kivik.Register(config.COUCHDB, &couchdbKivid.Couch{}) 31 | 32 | client, err := kivik.New(context.TODO(), dsc.Code, dsc.UrlAddress) 33 | if err != nil { 34 | return nil, errors.Wrap(err, "") 35 | } 36 | db, err := client.DB(context.TODO(), dsc.DbName) 37 | if err != nil { 38 | return nil, errors.Wrap(err, "") 39 | } 40 | c.Put(key, db) 41 | return db, nil 42 | 43 | } 44 | -------------------------------------------------------------------------------- /app/container/datastorefactory/datastoreFactory.go: -------------------------------------------------------------------------------- 1 | // Package datastorefactory using factory method pattern to create concrete database handler. 2 | // Datastore can be a database or a service ( for example, gRPC service or RESTFul service), which provides data access 3 | // for domain model. 4 | // There is only one method Build() for the factory and all different types of store following the same interface 5 | // to build the store connection. 6 | // Generally speaking, each data store need a separate factory. 7 | package datastorefactory 8 | 9 | import ( 10 | "github.com/jfeng45/servicetmpl1/app/config" 11 | "github.com/jfeng45/servicetmpl1/app/container" 12 | ) 13 | 14 | 15 | // To map "database code" to "database interface builder" 16 | // Concreate builder is in corresponding factory file. For example, "sqlFactory" is in "sqlFactory".go 17 | var dsFbMap = map[string]dsFbInterface{ 18 | config.SQLDB: &sqlFactory{}, 19 | config.COUCHDB: &couchdbFactory{}, 20 | config.CACHE_GRPC: &cacheGrpcFactory{}, 21 | } 22 | 23 | // DataStoreInterface serve as a marker to indicate the return type for Build method 24 | type DataStoreInterface interface{} 25 | 26 | // The builder interface for factory method pattern 27 | // Every factory needs to implement Build method 28 | type dsFbInterface interface { 29 | Build(container.Container, *config.DataStoreConfig) (DataStoreInterface, error) 30 | } 31 | 32 | //GetDataStoreFb is accessors for factoryBuilderMap 33 | func GetDataStoreFb(key string) dsFbInterface { 34 | return dsFbMap[key] 35 | } 36 | -------------------------------------------------------------------------------- /app/container/datastorefactory/sqlFactory.go: -------------------------------------------------------------------------------- 1 | package datastorefactory 2 | 3 | import ( 4 | "database/sql" 5 | 6 | databaseConfig "github.com/jfeng45/gtransaction/config" 7 | "github.com/jfeng45/gtransaction/factory" 8 | "github.com/jfeng45/gtransaction/gdbc" 9 | 10 | "github.com/jfeng45/servicetmpl1/app/config" 11 | "github.com/jfeng45/servicetmpl1/app/container" 12 | "github.com/jfeng45/servicetmpl1/app/logger" 13 | ) 14 | 15 | // sqlFactory is receiver for Build method 16 | type sqlFactory struct{} 17 | 18 | // implement Build method for SQL database 19 | func (sf *sqlFactory) Build(c container.Container, dsc *config.DataStoreConfig) (DataStoreInterface, error) { 20 | logger.Log.Debug("sqlFactory") 21 | key := dsc.Code 22 | // Only non-transaction connection is cached 23 | if !dsc.Tx { 24 | if value, found := c.Get(key); found { 25 | logger.Log.Debug("found db in container for key:", key) 26 | return value, nil 27 | } 28 | } 29 | tdbc := databaseConfig.DatabaseConfig{dsc.DriverName,dsc.UrlAddress, dsc.Tx} 30 | db, err := factory.BuildSqlDB(&tdbc) 31 | if err != nil { 32 | return nil, err 33 | } 34 | gdbc, err := buildGdbc(db, dsc.Tx) 35 | if err != nil { 36 | return nil, err 37 | } 38 | // Only non-transaction connection is cached 39 | if !dsc.Tx { 40 | c.Put(key, gdbc) 41 | } 42 | return gdbc, nil 43 | 44 | } 45 | 46 | func buildGdbc(sdb *sql.DB, tx bool) (gdbc.SqlGdbc, error) { 47 | var sdt gdbc.SqlGdbc 48 | if tx { 49 | tx, err := sdb.Begin() 50 | if err != nil { 51 | return nil, err 52 | } 53 | sdt = &gdbc.SqlConnTx{DB: tx} 54 | logger.Log.Debug("buildGdbc(), create TX:") 55 | } else { 56 | sdt = &gdbc.SqlDBTx{sdb} 57 | logger.Log.Debug("buildGdbc(), create DB:") 58 | } 59 | return sdt, nil 60 | } 61 | -------------------------------------------------------------------------------- /app/container/servicecontainer/serviceContainer.go: -------------------------------------------------------------------------------- 1 | package servicecontainer 2 | 3 | import ( 4 | "github.com/jfeng45/servicetmpl1/app/config" 5 | "github.com/jfeng45/servicetmpl1/app/container/usecasefactory" 6 | ) 7 | 8 | type ServiceContainer struct { 9 | FactoryMap map[string]interface{} 10 | AppConfig *config.AppConfig 11 | } 12 | 13 | func (sc *ServiceContainer) BuildUseCase(code string) (interface{}, error) { 14 | return usecasefactory.GetUseCaseFb(code).Build(sc, sc.AppConfig, code) 15 | } 16 | 17 | func (sc *ServiceContainer) Get(code string) (interface{}, bool) { 18 | value, found := sc.FactoryMap[code] 19 | return value, found 20 | } 21 | 22 | func (sc *ServiceContainer) Put(code string, value interface{}) { 23 | sc.FactoryMap[code] = value 24 | } 25 | -------------------------------------------------------------------------------- /app/container/usecasefactory/listUserFactory.go: -------------------------------------------------------------------------------- 1 | package usecasefactory 2 | 3 | import ( 4 | "github.com/jfeng45/servicetmpl1/app/config" 5 | "github.com/jfeng45/servicetmpl1/app/container" 6 | "github.com/jfeng45/servicetmpl1/domain/usecase/listuser" 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | type ListUserFactory struct{} 11 | 12 | func (luf *ListUserFactory) Build(c container.Container, appConfig *config.AppConfig, key string) (UseCaseInterface, error) { 13 | uc := appConfig.UseCaseConfig.ListUser 14 | 15 | udi, err := buildUserData(c, &uc.UserDataConfig) 16 | if err != nil { 17 | return nil, errors.Wrap(err, "") 18 | } 19 | cdi, err := buildCacheData(c, &uc.CacheDataConfig) 20 | luuc := listuser.ListUserUseCase{UserDataInterface: udi, CacheDataInterface: cdi} 21 | return &luuc, nil 22 | } 23 | -------------------------------------------------------------------------------- /app/container/usecasefactory/registrationFactory.go: -------------------------------------------------------------------------------- 1 | package usecasefactory 2 | 3 | import ( 4 | "github.com/jfeng45/servicetmpl1/app/config" 5 | "github.com/jfeng45/servicetmpl1/app/container" 6 | "github.com/jfeng45/servicetmpl1/domain/usecase/registration" 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | type RegistrationFactory struct { 11 | } 12 | 13 | // Build creates concrete type for RegistrationUseCaseInterface 14 | func (rf *RegistrationFactory) Build(c container.Container, appConfig *config.AppConfig, key string) (UseCaseInterface, error) { 15 | uc := appConfig.UseCaseConfig.Registration 16 | udi, err := buildUserData(c, &uc.UserDataConfig) 17 | if err != nil { 18 | return nil, errors.Wrap(err, "") 19 | } 20 | ruc := registration.RegistrationUseCase{UserDataInterface: udi} 21 | 22 | return &ruc, nil 23 | } 24 | -------------------------------------------------------------------------------- /app/container/usecasefactory/registrationTxFactory.go: -------------------------------------------------------------------------------- 1 | package usecasefactory 2 | 3 | import ( 4 | "github.com/jfeng45/servicetmpl1/app/config" 5 | "github.com/jfeng45/servicetmpl1/app/container" 6 | "github.com/jfeng45/servicetmpl1/domain/usecase/registration" 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | type RegistrationTxFactory struct { 11 | } 12 | 13 | // Build creates concrete type for RegistrationTxUseCaseInterface 14 | func (rtf *RegistrationTxFactory) Build(c container.Container, appConfig *config.AppConfig, key string) (UseCaseInterface, error) { 15 | uc := appConfig.UseCaseConfig.RegistrationTx 16 | udi, err := buildUserData(c, &uc.UserDataConfig) 17 | if err != nil { 18 | return nil, errors.Wrap(err, "") 19 | } 20 | ruc := registration.RegistrationTxUseCase{UserDataInterface: udi} 21 | 22 | return &ruc, nil 23 | } 24 | -------------------------------------------------------------------------------- /app/container/usecasefactory/useCaseFactory.go: -------------------------------------------------------------------------------- 1 | // Package usecasefactory using factory method pattern to create concrete case case. 2 | // Generally speaking, each use case needs a separate factory. 3 | package usecasefactory 4 | 5 | import ( 6 | "github.com/jfeng45/servicetmpl1/app/config" 7 | "github.com/jfeng45/servicetmpl1/app/container" 8 | ) 9 | 10 | //To map "use case code" to "use case interface builder" 11 | // Each use case has exactly one factory. For example, "registration" use case has "RegistrationFactory" 12 | // Each factory has it's own file. For example, "RegistrationFactory" is in "registrationFactory.go" 13 | var UseCaseFactoryBuilderMap = map[string]UseCaseFbInterface{ 14 | config.REGISTRATION: &RegistrationFactory{}, 15 | config.LIST_USER: &ListUserFactory{}, 16 | config.REGISTRATION_TX: &RegistrationTxFactory{}, 17 | } 18 | 19 | // UseCaseInterface serve as a marker to indicate the return type for Build method 20 | type UseCaseInterface interface{} 21 | 22 | // The builder interface for factory method pattern 23 | // Every factory needs to implement build method 24 | type UseCaseFbInterface interface { 25 | Build(c container.Container, appConfig *config.AppConfig, key string) (UseCaseInterface, error) 26 | } 27 | 28 | //GetDataStoreFb is accessors for factoryBuilderMap 29 | func GetUseCaseFb(key string) UseCaseFbInterface { 30 | return UseCaseFactoryBuilderMap[key] 31 | } 32 | -------------------------------------------------------------------------------- /app/container/usecasefactory/useCaseHelper.go: -------------------------------------------------------------------------------- 1 | package usecasefactory 2 | 3 | import ( 4 | "github.com/jfeng45/servicetmpl1/app/config" 5 | "github.com/jfeng45/servicetmpl1/app/container" 6 | "github.com/jfeng45/servicetmpl1/app/container/dataservicefactory" 7 | "github.com/jfeng45/servicetmpl1/applicationservice/dataservice" 8 | "github.com/pkg/errors" 9 | ) 10 | 11 | func buildUserData(c container.Container, dc *config.DataConfig) (dataservice.UserDataInterface, error) { 12 | dsi, err := dataservicefactory.GetDataServiceFb(dc.Code).Build(c, dc) 13 | if err != nil { 14 | return nil, errors.Wrap(err, "") 15 | } 16 | udi := dsi.(dataservice.UserDataInterface) 17 | return udi, nil 18 | } 19 | 20 | func buildCacheData(c container.Container, dc *config.DataConfig) (dataservice.CacheDataInterface, error) { 21 | //logger.Log.Debug("uc:", cdc) 22 | dsi, err := dataservicefactory.GetDataServiceFb(dc.Code).Build(c, dc) 23 | if err != nil { 24 | return nil, errors.Wrap(err, "") 25 | } 26 | cdi := dsi.(dataservice.CacheDataInterface) 27 | return cdi, nil 28 | } 29 | 30 | -------------------------------------------------------------------------------- /app/logger/logger.go: -------------------------------------------------------------------------------- 1 | // Package logger represents a generic logging interface 2 | package logger 3 | 4 | import "github.com/jfeng45/glogger" 5 | 6 | // Log is a package level variable, every program should access logging function through "Log" 7 | var Log glogger.Logger 8 | 9 | // SetLogger is the setter for log variable, it should be the only way to assign value to log 10 | func SetLogger(newLogger glogger.Logger) { 11 | Log = newLogger 12 | } 13 | -------------------------------------------------------------------------------- /applicationservice/cacheclient/cacheClient.go: -------------------------------------------------------------------------------- 1 | // Package cacheclient is the wrapper around the third party gRPC Cache Micro-service. 2 | // It encapsulates the logic to call outside service, to make it transparent to the business logic layer. 3 | 4 | package cacheclient 5 | 6 | import ( 7 | "context" 8 | "github.com/jfeng45/servicetmpl1/app/logger" 9 | "github.com/jfeng45/servicetmpl1/applicationservice/cacheclient/generatedclient" 10 | "google.golang.org/grpc" 11 | ) 12 | 13 | // CacheDataGrpc represents the gRPC connection handler 14 | type CacheDataGrpc struct { 15 | Conn *grpc.ClientConn 16 | } 17 | 18 | // getCacheClient creates a gRPC client 19 | func getCacheClient(conn *grpc.ClientConn) generatedclient.CacheServiceClient { 20 | return generatedclient.NewCacheServiceClient(conn) 21 | } 22 | 23 | // Get makes a call to Get function on Cache service 24 | func (cdg CacheDataGrpc) Get(key string) ([]byte, error) { 25 | cacheClient := getCacheClient(cdg.Conn) 26 | resp, err := cacheClient.Get(context.Background(), &generatedclient.GetReq{Key: key}) 27 | if err != nil { 28 | return nil, err 29 | } else { 30 | return resp.Value, err 31 | } 32 | } 33 | 34 | // Store makes a call to Store function on Cache service 35 | func (cdg CacheDataGrpc) Store(key string, value []byte) error { 36 | cacheClient := getCacheClient(cdg.Conn) 37 | ctx := context.Background() 38 | _, err := cacheClient.Store(ctx, &generatedclient.StoreReq{Key: key, Value: value}) 39 | 40 | if err != nil { 41 | return err 42 | } else { 43 | logger.Log.Debug("store called") 44 | } 45 | return nil 46 | } 47 | -------------------------------------------------------------------------------- /applicationservice/cacheclient/generatedclient/cacheJin.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: cacheJin.proto 3 | 4 | package generatedclient 5 | 6 | import proto "github.com/golang/protobuf/proto" 7 | import fmt "fmt" 8 | import math "math" 9 | 10 | import ( 11 | context "golang.org/x/net/context" 12 | grpc "google.golang.org/grpc" 13 | ) 14 | 15 | // Reference imports to suppress errors if they are not otherwise used. 16 | var _ = proto.Marshal 17 | var _ = fmt.Errorf 18 | var _ = math.Inf 19 | 20 | // This is a compile-time assertion to ensure that this generated file 21 | // is compatible with the proto package it is being compiled against. 22 | // A compilation error at this line likely means your copy of the 23 | // proto package needs to be updated. 24 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 25 | 26 | type DumpReq struct { 27 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 28 | XXX_unrecognized []byte `json:"-"` 29 | XXX_sizecache int32 `json:"-"` 30 | } 31 | 32 | func (m *DumpReq) Reset() { *m = DumpReq{} } 33 | func (m *DumpReq) String() string { return proto.CompactTextString(m) } 34 | func (*DumpReq) ProtoMessage() {} 35 | func (*DumpReq) Descriptor() ([]byte, []int) { 36 | return fileDescriptor_cacheJin_de21aa9a66397cc7, []int{0} 37 | } 38 | func (m *DumpReq) XXX_Unmarshal(b []byte) error { 39 | return xxx_messageInfo_DumpReq.Unmarshal(m, b) 40 | } 41 | func (m *DumpReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 42 | return xxx_messageInfo_DumpReq.Marshal(b, m, deterministic) 43 | } 44 | func (dst *DumpReq) XXX_Merge(src proto.Message) { 45 | xxx_messageInfo_DumpReq.Merge(dst, src) 46 | } 47 | func (m *DumpReq) XXX_Size() int { 48 | return xxx_messageInfo_DumpReq.Size(m) 49 | } 50 | func (m *DumpReq) XXX_DiscardUnknown() { 51 | xxx_messageInfo_DumpReq.DiscardUnknown(m) 52 | } 53 | 54 | var xxx_messageInfo_DumpReq proto.InternalMessageInfo 55 | 56 | type DumpItem struct { 57 | Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` 58 | Val []byte `protobuf:"bytes,2,opt,name=val,proto3" json:"val,omitempty"` 59 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 60 | XXX_unrecognized []byte `json:"-"` 61 | XXX_sizecache int32 `json:"-"` 62 | } 63 | 64 | func (m *DumpItem) Reset() { *m = DumpItem{} } 65 | func (m *DumpItem) String() string { return proto.CompactTextString(m) } 66 | func (*DumpItem) ProtoMessage() {} 67 | func (*DumpItem) Descriptor() ([]byte, []int) { 68 | return fileDescriptor_cacheJin_de21aa9a66397cc7, []int{1} 69 | } 70 | func (m *DumpItem) XXX_Unmarshal(b []byte) error { 71 | return xxx_messageInfo_DumpItem.Unmarshal(m, b) 72 | } 73 | func (m *DumpItem) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 74 | return xxx_messageInfo_DumpItem.Marshal(b, m, deterministic) 75 | } 76 | func (dst *DumpItem) XXX_Merge(src proto.Message) { 77 | xxx_messageInfo_DumpItem.Merge(dst, src) 78 | } 79 | func (m *DumpItem) XXX_Size() int { 80 | return xxx_messageInfo_DumpItem.Size(m) 81 | } 82 | func (m *DumpItem) XXX_DiscardUnknown() { 83 | xxx_messageInfo_DumpItem.DiscardUnknown(m) 84 | } 85 | 86 | var xxx_messageInfo_DumpItem proto.InternalMessageInfo 87 | 88 | func (m *DumpItem) GetKey() string { 89 | if m != nil { 90 | return m.Key 91 | } 92 | return "" 93 | } 94 | 95 | func (m *DumpItem) GetVal() []byte { 96 | if m != nil { 97 | return m.Val 98 | } 99 | return nil 100 | } 101 | 102 | type StoreReq struct { 103 | Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` 104 | Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` 105 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 106 | XXX_unrecognized []byte `json:"-"` 107 | XXX_sizecache int32 `json:"-"` 108 | } 109 | 110 | func (m *StoreReq) Reset() { *m = StoreReq{} } 111 | func (m *StoreReq) String() string { return proto.CompactTextString(m) } 112 | func (*StoreReq) ProtoMessage() {} 113 | func (*StoreReq) Descriptor() ([]byte, []int) { 114 | return fileDescriptor_cacheJin_de21aa9a66397cc7, []int{2} 115 | } 116 | func (m *StoreReq) XXX_Unmarshal(b []byte) error { 117 | return xxx_messageInfo_StoreReq.Unmarshal(m, b) 118 | } 119 | func (m *StoreReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 120 | return xxx_messageInfo_StoreReq.Marshal(b, m, deterministic) 121 | } 122 | func (dst *StoreReq) XXX_Merge(src proto.Message) { 123 | xxx_messageInfo_StoreReq.Merge(dst, src) 124 | } 125 | func (m *StoreReq) XXX_Size() int { 126 | return xxx_messageInfo_StoreReq.Size(m) 127 | } 128 | func (m *StoreReq) XXX_DiscardUnknown() { 129 | xxx_messageInfo_StoreReq.DiscardUnknown(m) 130 | } 131 | 132 | var xxx_messageInfo_StoreReq proto.InternalMessageInfo 133 | 134 | func (m *StoreReq) GetKey() string { 135 | if m != nil { 136 | return m.Key 137 | } 138 | return "" 139 | } 140 | 141 | func (m *StoreReq) GetValue() []byte { 142 | if m != nil { 143 | return m.Value 144 | } 145 | return nil 146 | } 147 | 148 | type StoreResp struct { 149 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 150 | XXX_unrecognized []byte `json:"-"` 151 | XXX_sizecache int32 `json:"-"` 152 | } 153 | 154 | func (m *StoreResp) Reset() { *m = StoreResp{} } 155 | func (m *StoreResp) String() string { return proto.CompactTextString(m) } 156 | func (*StoreResp) ProtoMessage() {} 157 | func (*StoreResp) Descriptor() ([]byte, []int) { 158 | return fileDescriptor_cacheJin_de21aa9a66397cc7, []int{3} 159 | } 160 | func (m *StoreResp) XXX_Unmarshal(b []byte) error { 161 | return xxx_messageInfo_StoreResp.Unmarshal(m, b) 162 | } 163 | func (m *StoreResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 164 | return xxx_messageInfo_StoreResp.Marshal(b, m, deterministic) 165 | } 166 | func (dst *StoreResp) XXX_Merge(src proto.Message) { 167 | xxx_messageInfo_StoreResp.Merge(dst, src) 168 | } 169 | func (m *StoreResp) XXX_Size() int { 170 | return xxx_messageInfo_StoreResp.Size(m) 171 | } 172 | func (m *StoreResp) XXX_DiscardUnknown() { 173 | xxx_messageInfo_StoreResp.DiscardUnknown(m) 174 | } 175 | 176 | var xxx_messageInfo_StoreResp proto.InternalMessageInfo 177 | 178 | type GetReq struct { 179 | Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` 180 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 181 | XXX_unrecognized []byte `json:"-"` 182 | XXX_sizecache int32 `json:"-"` 183 | } 184 | 185 | func (m *GetReq) Reset() { *m = GetReq{} } 186 | func (m *GetReq) String() string { return proto.CompactTextString(m) } 187 | func (*GetReq) ProtoMessage() {} 188 | func (*GetReq) Descriptor() ([]byte, []int) { 189 | return fileDescriptor_cacheJin_de21aa9a66397cc7, []int{4} 190 | } 191 | func (m *GetReq) XXX_Unmarshal(b []byte) error { 192 | return xxx_messageInfo_GetReq.Unmarshal(m, b) 193 | } 194 | func (m *GetReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 195 | return xxx_messageInfo_GetReq.Marshal(b, m, deterministic) 196 | } 197 | func (dst *GetReq) XXX_Merge(src proto.Message) { 198 | xxx_messageInfo_GetReq.Merge(dst, src) 199 | } 200 | func (m *GetReq) XXX_Size() int { 201 | return xxx_messageInfo_GetReq.Size(m) 202 | } 203 | func (m *GetReq) XXX_DiscardUnknown() { 204 | xxx_messageInfo_GetReq.DiscardUnknown(m) 205 | } 206 | 207 | var xxx_messageInfo_GetReq proto.InternalMessageInfo 208 | 209 | func (m *GetReq) GetKey() string { 210 | if m != nil { 211 | return m.Key 212 | } 213 | return "" 214 | } 215 | 216 | type GetResp struct { 217 | Value []byte `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` 218 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 219 | XXX_unrecognized []byte `json:"-"` 220 | XXX_sizecache int32 `json:"-"` 221 | } 222 | 223 | func (m *GetResp) Reset() { *m = GetResp{} } 224 | func (m *GetResp) String() string { return proto.CompactTextString(m) } 225 | func (*GetResp) ProtoMessage() {} 226 | func (*GetResp) Descriptor() ([]byte, []int) { 227 | return fileDescriptor_cacheJin_de21aa9a66397cc7, []int{5} 228 | } 229 | func (m *GetResp) XXX_Unmarshal(b []byte) error { 230 | return xxx_messageInfo_GetResp.Unmarshal(m, b) 231 | } 232 | func (m *GetResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 233 | return xxx_messageInfo_GetResp.Marshal(b, m, deterministic) 234 | } 235 | func (dst *GetResp) XXX_Merge(src proto.Message) { 236 | xxx_messageInfo_GetResp.Merge(dst, src) 237 | } 238 | func (m *GetResp) XXX_Size() int { 239 | return xxx_messageInfo_GetResp.Size(m) 240 | } 241 | func (m *GetResp) XXX_DiscardUnknown() { 242 | xxx_messageInfo_GetResp.DiscardUnknown(m) 243 | } 244 | 245 | var xxx_messageInfo_GetResp proto.InternalMessageInfo 246 | 247 | func (m *GetResp) GetValue() []byte { 248 | if m != nil { 249 | return m.Value 250 | } 251 | return nil 252 | } 253 | 254 | func init() { 255 | proto.RegisterType((*DumpReq)(nil), "reservegrpc.DumpReq") 256 | proto.RegisterType((*DumpItem)(nil), "reservegrpc.DumpItem") 257 | proto.RegisterType((*StoreReq)(nil), "reservegrpc.StoreReq") 258 | proto.RegisterType((*StoreResp)(nil), "reservegrpc.StoreResp") 259 | proto.RegisterType((*GetReq)(nil), "reservegrpc.GetReq") 260 | proto.RegisterType((*GetResp)(nil), "reservegrpc.GetResp") 261 | } 262 | 263 | // Reference imports to suppress errors if they are not otherwise used. 264 | var _ context.Context 265 | var _ grpc.ClientConn 266 | 267 | // This is a compile-time assertion to ensure that this generated file 268 | // is compatible with the grpc package it is being compiled against. 269 | const _ = grpc.SupportPackageIsVersion4 270 | 271 | // CacheServiceClient is the client API for CacheService service. 272 | // 273 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. 274 | type CacheServiceClient interface { 275 | Store(ctx context.Context, in *StoreReq, opts ...grpc.CallOption) (*StoreResp, error) 276 | Get(ctx context.Context, in *GetReq, opts ...grpc.CallOption) (*GetResp, error) 277 | Dump(ctx context.Context, in *DumpReq, opts ...grpc.CallOption) (CacheService_DumpClient, error) 278 | } 279 | 280 | type cacheServiceClient struct { 281 | cc *grpc.ClientConn 282 | } 283 | 284 | func NewCacheServiceClient(cc *grpc.ClientConn) CacheServiceClient { 285 | return &cacheServiceClient{cc} 286 | } 287 | 288 | func (c *cacheServiceClient) Store(ctx context.Context, in *StoreReq, opts ...grpc.CallOption) (*StoreResp, error) { 289 | out := new(StoreResp) 290 | err := c.cc.Invoke(ctx, "/reservegrpc.CacheService/Store", in, out, opts...) 291 | if err != nil { 292 | return nil, err 293 | } 294 | return out, nil 295 | } 296 | 297 | func (c *cacheServiceClient) Get(ctx context.Context, in *GetReq, opts ...grpc.CallOption) (*GetResp, error) { 298 | out := new(GetResp) 299 | err := c.cc.Invoke(ctx, "/reservegrpc.CacheService/Get", in, out, opts...) 300 | if err != nil { 301 | return nil, err 302 | } 303 | return out, nil 304 | } 305 | 306 | func (c *cacheServiceClient) Dump(ctx context.Context, in *DumpReq, opts ...grpc.CallOption) (CacheService_DumpClient, error) { 307 | stream, err := c.cc.NewStream(ctx, &_CacheService_serviceDesc.Streams[0], "/reservegrpc.CacheService/Dump", opts...) 308 | if err != nil { 309 | return nil, err 310 | } 311 | x := &cacheServiceDumpClient{stream} 312 | if err := x.ClientStream.SendMsg(in); err != nil { 313 | return nil, err 314 | } 315 | if err := x.ClientStream.CloseSend(); err != nil { 316 | return nil, err 317 | } 318 | return x, nil 319 | } 320 | 321 | type CacheService_DumpClient interface { 322 | Recv() (*DumpItem, error) 323 | grpc.ClientStream 324 | } 325 | 326 | type cacheServiceDumpClient struct { 327 | grpc.ClientStream 328 | } 329 | 330 | func (x *cacheServiceDumpClient) Recv() (*DumpItem, error) { 331 | m := new(DumpItem) 332 | if err := x.ClientStream.RecvMsg(m); err != nil { 333 | return nil, err 334 | } 335 | return m, nil 336 | } 337 | 338 | // CacheServiceServer is the server API for CacheService service. 339 | type CacheServiceServer interface { 340 | Store(context.Context, *StoreReq) (*StoreResp, error) 341 | Get(context.Context, *GetReq) (*GetResp, error) 342 | Dump(*DumpReq, CacheService_DumpServer) error 343 | } 344 | 345 | func RegisterCacheServiceServer(s *grpc.Server, srv CacheServiceServer) { 346 | s.RegisterService(&_CacheService_serviceDesc, srv) 347 | } 348 | 349 | func _CacheService_Store_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 350 | in := new(StoreReq) 351 | if err := dec(in); err != nil { 352 | return nil, err 353 | } 354 | if interceptor == nil { 355 | return srv.(CacheServiceServer).Store(ctx, in) 356 | } 357 | info := &grpc.UnaryServerInfo{ 358 | Server: srv, 359 | FullMethod: "/reservegrpc.CacheService/Store", 360 | } 361 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 362 | return srv.(CacheServiceServer).Store(ctx, req.(*StoreReq)) 363 | } 364 | return interceptor(ctx, in, info, handler) 365 | } 366 | 367 | func _CacheService_Get_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 368 | in := new(GetReq) 369 | if err := dec(in); err != nil { 370 | return nil, err 371 | } 372 | if interceptor == nil { 373 | return srv.(CacheServiceServer).Get(ctx, in) 374 | } 375 | info := &grpc.UnaryServerInfo{ 376 | Server: srv, 377 | FullMethod: "/reservegrpc.CacheService/Get", 378 | } 379 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 380 | return srv.(CacheServiceServer).Get(ctx, req.(*GetReq)) 381 | } 382 | return interceptor(ctx, in, info, handler) 383 | } 384 | 385 | func _CacheService_Dump_Handler(srv interface{}, stream grpc.ServerStream) error { 386 | m := new(DumpReq) 387 | if err := stream.RecvMsg(m); err != nil { 388 | return err 389 | } 390 | return srv.(CacheServiceServer).Dump(m, &cacheServiceDumpServer{stream}) 391 | } 392 | 393 | type CacheService_DumpServer interface { 394 | Send(*DumpItem) error 395 | grpc.ServerStream 396 | } 397 | 398 | type cacheServiceDumpServer struct { 399 | grpc.ServerStream 400 | } 401 | 402 | func (x *cacheServiceDumpServer) Send(m *DumpItem) error { 403 | return x.ServerStream.SendMsg(m) 404 | } 405 | 406 | var _CacheService_serviceDesc = grpc.ServiceDesc{ 407 | ServiceName: "reservegrpc.CacheService", 408 | HandlerType: (*CacheServiceServer)(nil), 409 | Methods: []grpc.MethodDesc{ 410 | { 411 | MethodName: "Store", 412 | Handler: _CacheService_Store_Handler, 413 | }, 414 | { 415 | MethodName: "Get", 416 | Handler: _CacheService_Get_Handler, 417 | }, 418 | }, 419 | Streams: []grpc.StreamDesc{ 420 | { 421 | StreamName: "Dump", 422 | Handler: _CacheService_Dump_Handler, 423 | ServerStreams: true, 424 | }, 425 | }, 426 | Metadata: "cacheJin.proto", 427 | } 428 | 429 | func init() { proto.RegisterFile("cacheJin.proto", fileDescriptor_cacheJin_de21aa9a66397cc7) } 430 | 431 | var fileDescriptor_cacheJin_de21aa9a66397cc7 = []byte{ 432 | // 242 bytes of a gzipped FileDescriptorProto 433 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x51, 0x4d, 0x4b, 0x03, 0x31, 434 | 0x10, 0x6d, 0xac, 0xfd, 0xd8, 0x69, 0x11, 0x89, 0xad, 0x94, 0x5c, 0x2c, 0x73, 0xea, 0x29, 0xc8, 435 | 0x7a, 0xd0, 0xbb, 0x42, 0xd1, 0x63, 0xfa, 0x0b, 0xd6, 0x30, 0x68, 0xb1, 0x75, 0xd3, 0x24, 0x0d, 436 | 0xf8, 0xc3, 0xfc, 0x7f, 0x92, 0x74, 0xa5, 0xbb, 0xec, 0xde, 0x5e, 0xde, 0xcc, 0x7b, 0xcc, 0x7b, 437 | 0x81, 0x2b, 0x5d, 0xe8, 0x4f, 0x7a, 0xdb, 0x7e, 0x4b, 0x63, 0x4b, 0x5f, 0xf2, 0x89, 0x25, 0x47, 438 | 0x36, 0xd0, 0x87, 0x35, 0x1a, 0x33, 0x18, 0xbd, 0x1c, 0xf7, 0x46, 0xd1, 0x01, 0x25, 0x8c, 0x23, 439 | 0x7c, 0xf5, 0xb4, 0xe7, 0xd7, 0xd0, 0xff, 0xa2, 0x9f, 0x05, 0x5b, 0xb2, 0x55, 0xa6, 0x22, 0x8c, 440 | 0x4c, 0x28, 0x76, 0x8b, 0x8b, 0x25, 0x5b, 0x4d, 0x55, 0x84, 0x98, 0xc3, 0x78, 0xe3, 0x4b, 0x4b, 441 | 0x8a, 0x0e, 0x1d, 0xfb, 0x33, 0x18, 0x84, 0x62, 0x77, 0xa4, 0x4a, 0x71, 0x7a, 0xe0, 0x04, 0xb2, 442 | 0x4a, 0xe3, 0x0c, 0x0a, 0x18, 0xae, 0xc9, 0x77, 0xca, 0xf1, 0x0e, 0x46, 0x69, 0xe6, 0xcc, 0xd9, 443 | 0x89, 0xd5, 0x9c, 0xf2, 0x5f, 0x06, 0xd3, 0xe7, 0x18, 0x6c, 0x43, 0x36, 0x6c, 0x35, 0xf1, 0x27, 444 | 0x18, 0x24, 0x6b, 0x3e, 0x97, 0xb5, 0x80, 0xf2, 0xff, 0x44, 0x71, 0xdb, 0x45, 0x3b, 0x83, 0x3d, 445 | 0x9e, 0x43, 0x7f, 0x4d, 0x9e, 0xdf, 0x34, 0x16, 0x4e, 0x97, 0x89, 0x59, 0x9b, 0x4c, 0x9a, 0x47, 446 | 0xb8, 0x8c, 0x65, 0xf1, 0xe6, 0xbc, 0xaa, 0x52, 0xcc, 0x5b, 0x6c, 0x6c, 0x15, 0x7b, 0xf7, 0xec, 447 | 0x7d, 0x98, 0x3e, 0xe1, 0xe1, 0x2f, 0x00, 0x00, 0xff, 0xff, 0x84, 0x90, 0x28, 0x3f, 0x96, 0x01, 448 | 0x00, 0x00, 449 | } 450 | -------------------------------------------------------------------------------- /applicationservice/cacheclient/generatedclient/doc.go: -------------------------------------------------------------------------------- 1 | //Package generatedclient is created to save the generated client lib for gRPC call. 2 | package generatedclient 3 | -------------------------------------------------------------------------------- /applicationservice/dataservice/dataService.go: -------------------------------------------------------------------------------- 1 | // Package dataservice and it's sub-package represents data persistence service, mainly access database, 2 | // but also including data persisted by other Micro-service. 3 | // For Micro-server, only the interface is defined in this package, the data transformation code is in adapter package 4 | // This is the top level package and it only defines interface, and all implementations are defined in sub-package 5 | // Use case package depends on it. 6 | package dataservice 7 | 8 | import ( 9 | "github.com/jfeng45/gtransaction/txdataservice" 10 | "github.com/jfeng45/servicetmpl1/domain/model" 11 | ) 12 | 13 | // UserDataInterface represents interface for user data access through database 14 | type UserDataInterface interface { 15 | // Remove deletes a user by user name from database. 16 | Remove(username string) (rowsAffected int64, err error) 17 | // Find retrieves a user from database based on a user's id 18 | Find(id int) (*model.User, error) 19 | // FindByName retrieves a user from database by User.Name 20 | FindByName(name string) (user *model.User, err error) 21 | // FindAll retrieves all users from database as an array of user 22 | FindAll() ([]model.User, error) 23 | // Update changes user information on the User.Id passed in. 24 | Update(user *model.User) (rowsAffected int64, err error) 25 | // Insert adds a user to a database. The returned resultUser has a Id, which is auto generated by database 26 | Insert(user *model.User) (resultUser *model.User, err error) 27 | // Add transaction support to the data interface 28 | txdataservice.TxDataInterface 29 | } 30 | 31 | // CacheDataInterface represents interface for cache service, which is a micro-service 32 | type CacheDataInterface interface { 33 | // Get handles call to Get function on Cache service 34 | Get(key string) ([]byte, error) 35 | // Store handles call to Store function on Cache service 36 | Store(key string, value []byte) error 37 | } 38 | 39 | 40 | -------------------------------------------------------------------------------- /applicationservice/dataservice/userdata/couchdb/userDataCouchdb.go: -------------------------------------------------------------------------------- 1 | // Package couchdb represents the CouchDB implementation of the user data persistence layer 2 | package couchdb 3 | 4 | import ( 5 | "context" 6 | "github.com/go-kivik/kivik" 7 | "github.com/jfeng45/servicetmpl1/app/logger" 8 | "github.com/jfeng45/servicetmpl1/domain/model" 9 | "github.com/pkg/errors" 10 | ) 11 | 12 | const ( 13 | DDOC string = "_design/serviceConfigDesignDoc" 14 | VIEW_ID string = "_view/serviceConfigByID" 15 | ) 16 | 17 | type UserDataCouchdb struct { 18 | DB *kivik.DB 19 | } 20 | 21 | // Create a view for "Find()", only need to run once. 22 | // Should be created by Fauxton, but you may not know how to do it. To make it easy for you, put it in code. 23 | // This function is created to make it easy to run the application, don't do it in production code 24 | // When run for more than once, it will show error "Conflict: Document update conflict", just ignore it 25 | func createView(udc *UserDataCouchdb) { 26 | rev, err := udc.DB.Put(context.TODO(), DDOC, map[string]interface{}{ 27 | "_id": DDOC, 28 | "views": map[string]interface{}{ 29 | "serviceConfigByID": map[string]interface{}{ 30 | "map": "function(doc) {\n if (doc.uid) {\n emit(doc.uid, doc);\n}\n}", 31 | }, 32 | }, 33 | "language": "javascript", 34 | }) 35 | // For each rnu after first, it will throw an error because it already exist. Just ignore it. 36 | if err != nil { 37 | logger.Log.Errorf("err:%v\n", err) 38 | } 39 | logger.Log.Debug("rev:", rev) 40 | } 41 | 42 | func (udc *UserDataCouchdb) Find(id int) (*model.User, error) { 43 | var err error 44 | // only need to do it once 45 | createView(udc) 46 | rows, err := udc.DB.Query(context.TODO(), DDOC, VIEW_ID, map[string]interface{}{"reduce": false}, 47 | kivik.Options{"key": id}) 48 | 49 | if err != nil { 50 | return nil, errors.Wrap(err, "") 51 | } 52 | var user *model.User 53 | if rows.Next() { 54 | user = &model.User{} 55 | if err := rows.ScanValue(user); err != nil { 56 | return nil, errors.Wrap(err, "") 57 | } 58 | } 59 | logger.Log.Debugf("view:%+v", user) 60 | 61 | if rows.Err() != nil { 62 | return nil, errors.Wrap(rows.Err(), "") 63 | } 64 | return user, nil 65 | } 66 | 67 | //The simple version (no need for view) of Find() to get it work, it is kind cheating because it didn't use the parameter id. 68 | //func (udc *UserDataCouchdb) Find(id int) (*model.User, error) { 69 | // _id :="80a9134c7dfa53f67f6be214e1000fa7" 70 | // row, err :=udc.DB.Get(context.TODO(), _id) 71 | // if err != nil { 72 | // return nil, errors.Wrap(err, "") 73 | // } 74 | // var user model.User 75 | // if err=row.ScanDoc(&user); err!=nil { 76 | // panic(err) 77 | // } 78 | // logger.Log.Debugf("user:", user) 79 | // return &user, nil 80 | //} 81 | 82 | func (udc *UserDataCouchdb) Remove(username string) (int64, error) { 83 | 84 | return 0, nil 85 | } 86 | func (udc *UserDataCouchdb) Update(user *model.User) (int64, error) { 87 | return 0, nil 88 | } 89 | 90 | func (udc *UserDataCouchdb) Insert(user *model.User) (*model.User, error) { 91 | return nil, nil 92 | } 93 | func (udc *UserDataCouchdb) FindAll() ([]model.User, error) { 94 | return []model.User{}, nil 95 | } 96 | func (udc *UserDataCouchdb) FindByName(name string) (*model.User, error) { 97 | return nil, nil 98 | } 99 | 100 | // EnableTx is created to satisfied the txdataservice.TxDataInterface, but it will never be used because NoSQL won't 101 | // support transaction 102 | func (udc *UserDataCouchdb) EnableTx(txFunc func() error) error { 103 | return nil 104 | } 105 | 106 | -------------------------------------------------------------------------------- /applicationservice/dataservice/userdata/sqldb/userDataSql.go: -------------------------------------------------------------------------------- 1 | // Package sql represents SQL database implementation of the user data persistence layer 2 | package sqldb 3 | 4 | import ( 5 | "database/sql" 6 | _ "github.com/go-sql-driver/mysql" 7 | "github.com/jfeng45/gtransaction/gdbc" 8 | "github.com/jfeng45/servicetmpl1/app/logger" 9 | "github.com/jfeng45/servicetmpl1/domain/model" 10 | "github.com/jfeng45/servicetmpl1/tool/timea" 11 | "github.com/pkg/errors" 12 | "time" 13 | ) 14 | 15 | const ( 16 | // test rollback 17 | //DELETE_USER string = "delete from userinf where username=?" 18 | DELETE_USER string = "delete from userinfo where username=?" 19 | QUERY_USER_BY_ID string = "SELECT * FROM userinfo where uid =?" 20 | QUERY_USER_BY_NAME = "SELECT * FROM userinfo where username =?" 21 | QUERY_USER = "SELECT * FROM userinfo " 22 | UPDATE_USER = "update userinfo set username=?, department=?, created=? where uid=?" 23 | INSERT_USER = "INSERT userinfo SET username=?,department=?,created=?" 24 | ) 25 | 26 | // UserDataSql is the SQL implementation of UserDataInterface 27 | type UserDataSql struct { 28 | DB gdbc.SqlGdbc 29 | } 30 | 31 | func (uds *UserDataSql) Remove(username string) (int64, error) { 32 | 33 | stmt, err := uds.DB.Prepare(DELETE_USER) 34 | if err != nil { 35 | return 0, errors.Wrap(err, "") 36 | } 37 | defer stmt.Close() 38 | 39 | res, err := stmt.Exec(username) 40 | if err != nil { 41 | return 0, errors.Wrap(err, "") 42 | } 43 | rowsAffected, err := res.RowsAffected() 44 | if err != nil { 45 | return 0, errors.Wrap(err, "") 46 | } 47 | 48 | logger.Log.Debug("remove:row affected ", rowsAffected) 49 | return rowsAffected, nil 50 | } 51 | 52 | func (uds *UserDataSql) Find(id int) (*model.User, error) { 53 | rows, err := uds.DB.Query(QUERY_USER_BY_ID, id) 54 | if err != nil { 55 | return nil, errors.Wrap(err, "") 56 | } 57 | defer rows.Close() 58 | return retrieveUser(rows) 59 | } 60 | func retrieveUser(rows *sql.Rows) (*model.User, error) { 61 | if rows.Next() { 62 | return rowsToUser(rows) 63 | } 64 | return nil, nil 65 | } 66 | func rowsToUser(rows *sql.Rows) (*model.User, error) { 67 | var ds string 68 | user := &model.User{} 69 | err := rows.Scan(&user.Id, &user.Name, &user.Department, &ds) 70 | if err != nil { 71 | return nil, errors.Wrap(err, "") 72 | } 73 | created, err := time.Parse(timea.FORMAT_ISO8601_DATE, ds) 74 | if err != nil { 75 | return nil, errors.Wrap(err, "") 76 | } 77 | user.Created = created 78 | 79 | logger.Log.Debug("rows to User:", user) 80 | return user, nil 81 | } 82 | func (uds *UserDataSql) FindByName(name string) (*model.User, error) { 83 | //logger.Log.Debug("call FindByName() and name is:", name) 84 | rows, err := uds.DB.Query(QUERY_USER_BY_NAME, name) 85 | if err != nil { 86 | return nil, errors.Wrap(err, "") 87 | } 88 | defer rows.Close() 89 | return retrieveUser(rows) 90 | } 91 | 92 | func (uds *UserDataSql) FindAll() ([]model.User, error) { 93 | 94 | rows, err := uds.DB.Query(QUERY_USER) 95 | if err != nil { 96 | return nil, errors.Wrap(err, "") 97 | } 98 | defer rows.Close() 99 | users := []model.User{} 100 | 101 | //var ds string 102 | for rows.Next() { 103 | user, err := rowsToUser(rows) 104 | if err != nil { 105 | return users, errors.Wrap(err, "") 106 | } 107 | users = append(users, *user) 108 | 109 | } 110 | //need to check error for rows.Next() 111 | if err = rows.Err(); err != nil { 112 | return nil, errors.Wrap(err, "") 113 | } 114 | logger.Log.Debug("find user list:", users) 115 | return users, nil 116 | } 117 | 118 | func (uds *UserDataSql) Update(user *model.User) (int64, error) { 119 | 120 | stmt, err := uds.DB.Prepare(UPDATE_USER) 121 | 122 | if err != nil { 123 | return 0, errors.Wrap(err, "") 124 | } 125 | defer stmt.Close() 126 | res, err := stmt.Exec(user.Name, user.Department, user.Created, user.Id) 127 | if err != nil { 128 | return 0, errors.Wrap(err, "") 129 | } 130 | rowsAffected, err := res.RowsAffected() 131 | 132 | if err != nil { 133 | return 0, errors.Wrap(err, "") 134 | } 135 | logger.Log.Debug("update: rows affected: ", rowsAffected) 136 | 137 | return rowsAffected, nil 138 | } 139 | 140 | func (uds *UserDataSql) Insert(user *model.User) (*model.User, error) { 141 | 142 | stmt, err := uds.DB.Prepare(INSERT_USER) 143 | if err != nil { 144 | return nil, errors.Wrap(err, "") 145 | } 146 | defer stmt.Close() 147 | res, err := stmt.Exec(user.Name, user.Department, user.Created) 148 | if err != nil { 149 | return nil, errors.Wrap(err, "") 150 | } 151 | id, err := res.LastInsertId() 152 | if err != nil { 153 | return nil, errors.Wrap(err, "") 154 | } 155 | user.Id = int(id) 156 | logger.Log.Debug("user inserted:", user) 157 | return user, nil 158 | } 159 | 160 | func (uds *UserDataSql) EnableTx(txFunc func() error) error { 161 | return uds.DB.TxEnd(txFunc) 162 | } 163 | 164 | -------------------------------------------------------------------------------- /applicationservice/doc.go: -------------------------------------------------------------------------------- 1 | // Package applicationservice represents varies third part resource need to be accessed. Don't be confused with "tool" 2 | // package, which is for third part library as well, but is mainly for lower level library. "applicationservice" package, 3 | // on the other hand, is mainly for data access on business layer 4 | package applicationservice 5 | -------------------------------------------------------------------------------- /applicationservice/paymentclient/paymentClient.go: -------------------------------------------------------------------------------- 1 | // Package paymentclient is created to show the project structure, no real use. 2 | package paymentclient 3 | -------------------------------------------------------------------------------- /applicationservice/userclient/generatedclient/usergrpc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: usergrpc.proto 3 | 4 | package generatedclient 5 | 6 | import proto "github.com/golang/protobuf/proto" 7 | import fmt "fmt" 8 | import math "math" 9 | import timestamp "github.com/golang/protobuf/ptypes/timestamp" 10 | 11 | import ( 12 | context "golang.org/x/net/context" 13 | grpc "google.golang.org/grpc" 14 | ) 15 | 16 | // Reference imports to suppress errors if they are not otherwise used. 17 | var _ = proto.Marshal 18 | var _ = fmt.Errorf 19 | var _ = math.Inf 20 | 21 | // This is a compile-time assertion to ensure that this generated file 22 | // is compatible with the proto package it is being compiled against. 23 | // A compilation error at this line likely means your copy of the 24 | // proto package needs to be updated. 25 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 26 | 27 | type ListUserReq struct { 28 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 29 | XXX_unrecognized []byte `json:"-"` 30 | XXX_sizecache int32 `json:"-"` 31 | } 32 | 33 | func (m *ListUserReq) Reset() { *m = ListUserReq{} } 34 | func (m *ListUserReq) String() string { return proto.CompactTextString(m) } 35 | func (*ListUserReq) ProtoMessage() {} 36 | func (*ListUserReq) Descriptor() ([]byte, []int) { 37 | return fileDescriptor_usergrpc_c653ab406bb3c1d5, []int{0} 38 | } 39 | func (m *ListUserReq) XXX_Unmarshal(b []byte) error { 40 | return xxx_messageInfo_ListUserReq.Unmarshal(m, b) 41 | } 42 | func (m *ListUserReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 43 | return xxx_messageInfo_ListUserReq.Marshal(b, m, deterministic) 44 | } 45 | func (dst *ListUserReq) XXX_Merge(src proto.Message) { 46 | xxx_messageInfo_ListUserReq.Merge(dst, src) 47 | } 48 | func (m *ListUserReq) XXX_Size() int { 49 | return xxx_messageInfo_ListUserReq.Size(m) 50 | } 51 | func (m *ListUserReq) XXX_DiscardUnknown() { 52 | xxx_messageInfo_ListUserReq.DiscardUnknown(m) 53 | } 54 | 55 | var xxx_messageInfo_ListUserReq proto.InternalMessageInfo 56 | 57 | type ListUserResp struct { 58 | User []*User `protobuf:"bytes,1,rep,name=user,proto3" json:"user,omitempty"` 59 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 60 | XXX_unrecognized []byte `json:"-"` 61 | XXX_sizecache int32 `json:"-"` 62 | } 63 | 64 | func (m *ListUserResp) Reset() { *m = ListUserResp{} } 65 | func (m *ListUserResp) String() string { return proto.CompactTextString(m) } 66 | func (*ListUserResp) ProtoMessage() {} 67 | func (*ListUserResp) Descriptor() ([]byte, []int) { 68 | return fileDescriptor_usergrpc_c653ab406bb3c1d5, []int{1} 69 | } 70 | func (m *ListUserResp) XXX_Unmarshal(b []byte) error { 71 | return xxx_messageInfo_ListUserResp.Unmarshal(m, b) 72 | } 73 | func (m *ListUserResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 74 | return xxx_messageInfo_ListUserResp.Marshal(b, m, deterministic) 75 | } 76 | func (dst *ListUserResp) XXX_Merge(src proto.Message) { 77 | xxx_messageInfo_ListUserResp.Merge(dst, src) 78 | } 79 | func (m *ListUserResp) XXX_Size() int { 80 | return xxx_messageInfo_ListUserResp.Size(m) 81 | } 82 | func (m *ListUserResp) XXX_DiscardUnknown() { 83 | xxx_messageInfo_ListUserResp.DiscardUnknown(m) 84 | } 85 | 86 | var xxx_messageInfo_ListUserResp proto.InternalMessageInfo 87 | 88 | func (m *ListUserResp) GetUser() []*User { 89 | if m != nil { 90 | return m.User 91 | } 92 | return nil 93 | } 94 | 95 | type User struct { 96 | Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` 97 | Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` 98 | Department string `protobuf:"bytes,3,opt,name=department,proto3" json:"department,omitempty"` 99 | Created *timestamp.Timestamp `protobuf:"bytes,4,opt,name=created,proto3" json:"created,omitempty"` 100 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 101 | XXX_unrecognized []byte `json:"-"` 102 | XXX_sizecache int32 `json:"-"` 103 | } 104 | 105 | func (m *User) Reset() { *m = User{} } 106 | func (m *User) String() string { return proto.CompactTextString(m) } 107 | func (*User) ProtoMessage() {} 108 | func (*User) Descriptor() ([]byte, []int) { 109 | return fileDescriptor_usergrpc_c653ab406bb3c1d5, []int{2} 110 | } 111 | func (m *User) XXX_Unmarshal(b []byte) error { 112 | return xxx_messageInfo_User.Unmarshal(m, b) 113 | } 114 | func (m *User) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 115 | return xxx_messageInfo_User.Marshal(b, m, deterministic) 116 | } 117 | func (dst *User) XXX_Merge(src proto.Message) { 118 | xxx_messageInfo_User.Merge(dst, src) 119 | } 120 | func (m *User) XXX_Size() int { 121 | return xxx_messageInfo_User.Size(m) 122 | } 123 | func (m *User) XXX_DiscardUnknown() { 124 | xxx_messageInfo_User.DiscardUnknown(m) 125 | } 126 | 127 | var xxx_messageInfo_User proto.InternalMessageInfo 128 | 129 | func (m *User) GetId() int32 { 130 | if m != nil { 131 | return m.Id 132 | } 133 | return 0 134 | } 135 | 136 | func (m *User) GetName() string { 137 | if m != nil { 138 | return m.Name 139 | } 140 | return "" 141 | } 142 | 143 | func (m *User) GetDepartment() string { 144 | if m != nil { 145 | return m.Department 146 | } 147 | return "" 148 | } 149 | 150 | func (m *User) GetCreated() *timestamp.Timestamp { 151 | if m != nil { 152 | return m.Created 153 | } 154 | return nil 155 | } 156 | 157 | type RegisterUserReq struct { 158 | User *User `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"` 159 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 160 | XXX_unrecognized []byte `json:"-"` 161 | XXX_sizecache int32 `json:"-"` 162 | } 163 | 164 | func (m *RegisterUserReq) Reset() { *m = RegisterUserReq{} } 165 | func (m *RegisterUserReq) String() string { return proto.CompactTextString(m) } 166 | func (*RegisterUserReq) ProtoMessage() {} 167 | func (*RegisterUserReq) Descriptor() ([]byte, []int) { 168 | return fileDescriptor_usergrpc_c653ab406bb3c1d5, []int{3} 169 | } 170 | func (m *RegisterUserReq) XXX_Unmarshal(b []byte) error { 171 | return xxx_messageInfo_RegisterUserReq.Unmarshal(m, b) 172 | } 173 | func (m *RegisterUserReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 174 | return xxx_messageInfo_RegisterUserReq.Marshal(b, m, deterministic) 175 | } 176 | func (dst *RegisterUserReq) XXX_Merge(src proto.Message) { 177 | xxx_messageInfo_RegisterUserReq.Merge(dst, src) 178 | } 179 | func (m *RegisterUserReq) XXX_Size() int { 180 | return xxx_messageInfo_RegisterUserReq.Size(m) 181 | } 182 | func (m *RegisterUserReq) XXX_DiscardUnknown() { 183 | xxx_messageInfo_RegisterUserReq.DiscardUnknown(m) 184 | } 185 | 186 | var xxx_messageInfo_RegisterUserReq proto.InternalMessageInfo 187 | 188 | func (m *RegisterUserReq) GetUser() *User { 189 | if m != nil { 190 | return m.User 191 | } 192 | return nil 193 | } 194 | 195 | type RegisterUserResp struct { 196 | User *User `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"` 197 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 198 | XXX_unrecognized []byte `json:"-"` 199 | XXX_sizecache int32 `json:"-"` 200 | } 201 | 202 | func (m *RegisterUserResp) Reset() { *m = RegisterUserResp{} } 203 | func (m *RegisterUserResp) String() string { return proto.CompactTextString(m) } 204 | func (*RegisterUserResp) ProtoMessage() {} 205 | func (*RegisterUserResp) Descriptor() ([]byte, []int) { 206 | return fileDescriptor_usergrpc_c653ab406bb3c1d5, []int{4} 207 | } 208 | func (m *RegisterUserResp) XXX_Unmarshal(b []byte) error { 209 | return xxx_messageInfo_RegisterUserResp.Unmarshal(m, b) 210 | } 211 | func (m *RegisterUserResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 212 | return xxx_messageInfo_RegisterUserResp.Marshal(b, m, deterministic) 213 | } 214 | func (dst *RegisterUserResp) XXX_Merge(src proto.Message) { 215 | xxx_messageInfo_RegisterUserResp.Merge(dst, src) 216 | } 217 | func (m *RegisterUserResp) XXX_Size() int { 218 | return xxx_messageInfo_RegisterUserResp.Size(m) 219 | } 220 | func (m *RegisterUserResp) XXX_DiscardUnknown() { 221 | xxx_messageInfo_RegisterUserResp.DiscardUnknown(m) 222 | } 223 | 224 | var xxx_messageInfo_RegisterUserResp proto.InternalMessageInfo 225 | 226 | func (m *RegisterUserResp) GetUser() *User { 227 | if m != nil { 228 | return m.User 229 | } 230 | return nil 231 | } 232 | 233 | func init() { 234 | proto.RegisterType((*ListUserReq)(nil), "generatedclient.ListUserReq") 235 | proto.RegisterType((*ListUserResp)(nil), "generatedclient.ListUserResp") 236 | proto.RegisterType((*User)(nil), "generatedclient.User") 237 | proto.RegisterType((*RegisterUserReq)(nil), "generatedclient.RegisterUserReq") 238 | proto.RegisterType((*RegisterUserResp)(nil), "generatedclient.RegisterUserResp") 239 | } 240 | 241 | // Reference imports to suppress errors if they are not otherwise used. 242 | var _ context.Context 243 | var _ grpc.ClientConn 244 | 245 | // This is a compile-time assertion to ensure that this generated file 246 | // is compatible with the grpc package it is being compiled against. 247 | const _ = grpc.SupportPackageIsVersion4 248 | 249 | // UserServiceClient is the client API for UserService service. 250 | // 251 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. 252 | type UserServiceClient interface { 253 | RegisterUser(ctx context.Context, in *RegisterUserReq, opts ...grpc.CallOption) (*RegisterUserResp, error) 254 | ListUser(ctx context.Context, in *ListUserReq, opts ...grpc.CallOption) (*ListUserResp, error) 255 | } 256 | 257 | type userServiceClient struct { 258 | cc *grpc.ClientConn 259 | } 260 | 261 | func NewUserServiceClient(cc *grpc.ClientConn) UserServiceClient { 262 | return &userServiceClient{cc} 263 | } 264 | 265 | func (c *userServiceClient) RegisterUser(ctx context.Context, in *RegisterUserReq, opts ...grpc.CallOption) (*RegisterUserResp, error) { 266 | out := new(RegisterUserResp) 267 | err := c.cc.Invoke(ctx, "/generatedclient.UserService/RegisterUser", in, out, opts...) 268 | if err != nil { 269 | return nil, err 270 | } 271 | return out, nil 272 | } 273 | 274 | func (c *userServiceClient) ListUser(ctx context.Context, in *ListUserReq, opts ...grpc.CallOption) (*ListUserResp, error) { 275 | out := new(ListUserResp) 276 | err := c.cc.Invoke(ctx, "/generatedclient.UserService/ListUser", in, out, opts...) 277 | if err != nil { 278 | return nil, err 279 | } 280 | return out, nil 281 | } 282 | 283 | // UserServiceServer is the server API for UserService service. 284 | type UserServiceServer interface { 285 | RegisterUser(context.Context, *RegisterUserReq) (*RegisterUserResp, error) 286 | ListUser(context.Context, *ListUserReq) (*ListUserResp, error) 287 | } 288 | 289 | func RegisterUserServiceServer(s *grpc.Server, srv UserServiceServer) { 290 | s.RegisterService(&_UserService_serviceDesc, srv) 291 | } 292 | 293 | func _UserService_RegisterUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 294 | in := new(RegisterUserReq) 295 | if err := dec(in); err != nil { 296 | return nil, err 297 | } 298 | if interceptor == nil { 299 | return srv.(UserServiceServer).RegisterUser(ctx, in) 300 | } 301 | info := &grpc.UnaryServerInfo{ 302 | Server: srv, 303 | FullMethod: "/generatedclient.UserService/RegisterUser", 304 | } 305 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 306 | return srv.(UserServiceServer).RegisterUser(ctx, req.(*RegisterUserReq)) 307 | } 308 | return interceptor(ctx, in, info, handler) 309 | } 310 | 311 | func _UserService_ListUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 312 | in := new(ListUserReq) 313 | if err := dec(in); err != nil { 314 | return nil, err 315 | } 316 | if interceptor == nil { 317 | return srv.(UserServiceServer).ListUser(ctx, in) 318 | } 319 | info := &grpc.UnaryServerInfo{ 320 | Server: srv, 321 | FullMethod: "/generatedclient.UserService/ListUser", 322 | } 323 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 324 | return srv.(UserServiceServer).ListUser(ctx, req.(*ListUserReq)) 325 | } 326 | return interceptor(ctx, in, info, handler) 327 | } 328 | 329 | var _UserService_serviceDesc = grpc.ServiceDesc{ 330 | ServiceName: "generatedclient.UserService", 331 | HandlerType: (*UserServiceServer)(nil), 332 | Methods: []grpc.MethodDesc{ 333 | { 334 | MethodName: "RegisterUser", 335 | Handler: _UserService_RegisterUser_Handler, 336 | }, 337 | { 338 | MethodName: "ListUser", 339 | Handler: _UserService_ListUser_Handler, 340 | }, 341 | }, 342 | Streams: []grpc.StreamDesc{}, 343 | Metadata: "usergrpc.proto", 344 | } 345 | 346 | func init() { proto.RegisterFile("usergrpc.proto", fileDescriptor_usergrpc_c653ab406bb3c1d5) } 347 | 348 | var fileDescriptor_usergrpc_c653ab406bb3c1d5 = []byte{ 349 | // 295 bytes of a gzipped FileDescriptorProto 350 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x91, 0x4d, 0x4e, 0xc3, 0x30, 351 | 0x10, 0x85, 0x71, 0x1a, 0xfe, 0x26, 0xa5, 0x45, 0x23, 0x21, 0x45, 0x11, 0x3f, 0x21, 0xab, 0xb0, 352 | 0x71, 0xa5, 0xc0, 0x06, 0x09, 0x0e, 0x80, 0xc4, 0x2a, 0xd0, 0x03, 0xa4, 0xc9, 0x10, 0x59, 0x6a, 353 | 0x12, 0x63, 0xbb, 0xac, 0xb9, 0x11, 0x57, 0x44, 0x71, 0x89, 0x48, 0x5b, 0xa9, 0xd9, 0x79, 0x3c, 354 | 0xef, 0xbd, 0xf1, 0xe7, 0x81, 0xc9, 0x4a, 0x93, 0x2a, 0x95, 0xcc, 0xb9, 0x54, 0x8d, 0x69, 0x70, 355 | 0x5a, 0x52, 0x4d, 0x2a, 0x33, 0x54, 0xe4, 0x4b, 0x41, 0xb5, 0x09, 0x6e, 0xca, 0xa6, 0x29, 0x97, 356 | 0x34, 0xb3, 0xed, 0xc5, 0xea, 0x63, 0x66, 0x44, 0x45, 0xda, 0x64, 0x95, 0x5c, 0x3b, 0xa2, 0x33, 357 | 0xf0, 0x5e, 0x85, 0x36, 0x73, 0x4d, 0x2a, 0xa5, 0xcf, 0xe8, 0x11, 0xc6, 0xff, 0xa5, 0x96, 0x78, 358 | 0x07, 0x6e, 0x3b, 0xc2, 0x67, 0xe1, 0x28, 0xf6, 0x92, 0x0b, 0xbe, 0x95, 0xcf, 0xad, 0xd0, 0x4a, 359 | 0xa2, 0x6f, 0x06, 0x6e, 0x5b, 0xe2, 0x04, 0x1c, 0x51, 0xf8, 0x2c, 0x64, 0xf1, 0x61, 0xea, 0x88, 360 | 0x02, 0x11, 0xdc, 0x3a, 0xab, 0xc8, 0x77, 0x42, 0x16, 0x9f, 0xa6, 0xf6, 0x8c, 0xd7, 0x00, 0x05, 361 | 0xc9, 0x4c, 0x99, 0x8a, 0x6a, 0xe3, 0x8f, 0x6c, 0xa7, 0x77, 0x83, 0x0f, 0x70, 0x9c, 0x2b, 0x6a, 362 | 0x07, 0xf9, 0x6e, 0xc8, 0x62, 0x2f, 0x09, 0xf8, 0x9a, 0x84, 0x77, 0x24, 0xfc, 0xbd, 0x23, 0x49, 363 | 0x3b, 0x69, 0xf4, 0x04, 0xd3, 0x94, 0x4a, 0xa1, 0x0d, 0xa9, 0x3f, 0xa0, 0x1e, 0x00, 0x1b, 0x02, 364 | 0x78, 0x86, 0xf3, 0x4d, 0xf7, 0x06, 0xff, 0x90, 0x3d, 0xf9, 0x61, 0xe0, 0xb5, 0xe5, 0x1b, 0xa9, 365 | 0x2f, 0x91, 0x13, 0xce, 0x61, 0xdc, 0x8f, 0xc3, 0x70, 0xc7, 0xbc, 0xf5, 0xd6, 0xe0, 0x76, 0x40, 366 | 0xa1, 0x65, 0x74, 0x80, 0x2f, 0x70, 0xd2, 0x6d, 0x08, 0x2f, 0x77, 0x0c, 0xbd, 0x5d, 0x06, 0x57, 367 | 0x7b, 0xba, 0x6d, 0xd4, 0xe2, 0xc8, 0xfe, 0xe5, 0xfd, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xd3, 368 | 0x04, 0xca, 0xf9, 0x46, 0x02, 0x00, 0x00, 369 | } 370 | -------------------------------------------------------------------------------- /applicationservice/userclient/generatedclient/usergrpc.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package generatedclient; 3 | 4 | import "google/protobuf/timestamp.proto"; 5 | 6 | service UserService { 7 | rpc RegisterUser(RegisterUserReq ) returns (RegisterUserResp) {} 8 | rpc ListUser(ListUserReq) returns (ListUserResp) {} 9 | } 10 | 11 | message ListUserReq { 12 | } 13 | message ListUserResp { 14 | repeated User user =1; 15 | } 16 | 17 | message User { 18 | int32 id = 1; 19 | string name = 2; 20 | string department =3; 21 | google.protobuf.Timestamp created =4; 22 | } 23 | message RegisterUserReq { 24 | User user = 1; 25 | } 26 | 27 | message RegisterUserResp { 28 | User user =1; 29 | } 30 | 31 | -------------------------------------------------------------------------------- /applicationservice/userclient/userGrpc.go: -------------------------------------------------------------------------------- 1 | // Package userclient is client library if you need to call the user Micro-service as a client. 2 | // It provides client library and the data transformation service. 3 | package userclient 4 | 5 | import ( 6 | "github.com/golang/protobuf/ptypes" 7 | "github.com/jfeng45/servicetmpl1/applicationservice/userclient/generatedclient" 8 | "github.com/jfeng45/servicetmpl1/domain/model" 9 | "github.com/pkg/errors" 10 | ) 11 | 12 | // GrpcToUser converts from grpc User type to domain Model user type 13 | func GrpcToUser(user *generatedclient.User) (*model.User, error) { 14 | if user == nil { 15 | return nil, nil 16 | } 17 | resultUser := model.User{} 18 | 19 | resultUser.Id = int(user.Id) 20 | resultUser.Name = user.Name 21 | resultUser.Department = user.Department 22 | created, err := ptypes.Timestamp(user.Created) 23 | if err != nil { 24 | return nil, errors.Wrap(err, "") 25 | } 26 | resultUser.Created = created 27 | return &resultUser, nil 28 | } 29 | 30 | // UserToGrpc converts from domain Model User type to grpc user type 31 | func UserToGrpc(user *model.User) (*generatedclient.User, error) { 32 | if user == nil { 33 | return nil, nil 34 | } 35 | resultUser := generatedclient.User{} 36 | resultUser.Id = int32(user.Id) 37 | resultUser.Name = user.Name 38 | resultUser.Department = user.Department 39 | created, err := ptypes.TimestampProto(user.Created) 40 | if err != nil { 41 | return nil, errors.Wrap(err, "") 42 | } 43 | resultUser.Created = created 44 | return &resultUser, nil 45 | } 46 | 47 | // UserListToGrpc converts from array of domain Model User type to array of grpc user type 48 | func UserListToGrpc(ul []model.User) ([]*generatedclient.User, error) { 49 | var gul []*generatedclient.User 50 | for _, user := range ul { 51 | gu, err := UserToGrpc(&user) 52 | if err != nil { 53 | return nil, errors.Wrap(err, "") 54 | } 55 | gul = append(gul, gu) 56 | } 57 | return gul, nil 58 | } 59 | -------------------------------------------------------------------------------- /cmd/grpcclient/grpcClientMain.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/golang/protobuf/ptypes" 7 | uspb "github.com/jfeng45/servicetmpl1/applicationservice/userclient/generatedclient" 8 | "golang.org/x/net/context" 9 | "google.golang.org/grpc" 10 | ) 11 | 12 | const ( 13 | TARGET string = "localhost:5052" 14 | ) 15 | 16 | func callRegisterUser(usc uspb.UserServiceClient) { 17 | ctx := context.Background() 18 | 19 | created := ptypes.TimestampNow() 20 | u := uspb.User{Name: "Tony", Department: "IT", Created: created} 21 | 22 | resp, err := usc.RegisterUser(ctx, &uspb.RegisterUserReq{User: &u}) 23 | 24 | if err != nil { 25 | fmt.Println(err) 26 | } else { 27 | fmt.Println("results user: ", resp.User) 28 | } 29 | } 30 | func callListUser(usc uspb.UserServiceClient) { 31 | 32 | resp, err := usc.ListUser(context.Background(), &uspb.ListUserReq{}) 33 | if err != nil { 34 | fmt.Println(err) 35 | } else { 36 | fmt.Printf("Got list users %s\n", resp.User) 37 | } 38 | } 39 | 40 | func main() { 41 | 42 | conn, err := grpc.Dial(TARGET, grpc.WithInsecure()) 43 | if err != nil { 44 | fmt.Errorf("failed to dial server: %v", err) 45 | } 46 | userServiceClient := uspb.NewUserServiceClient(conn) 47 | fmt.Println("client strated") 48 | 49 | callRegisterUser(userServiceClient) 50 | callListUser(userServiceClient) 51 | } 52 | -------------------------------------------------------------------------------- /cmd/grpcserver/grpcServerMain.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | 8 | "github.com/jfeng45/servicetmpl1/app" 9 | "github.com/jfeng45/servicetmpl1/app/container/containerhelper" 10 | "github.com/jfeng45/servicetmpl1/app/container/servicecontainer" 11 | "github.com/jfeng45/servicetmpl1/app/logger" 12 | "github.com/jfeng45/servicetmpl1/applicationservice/userclient" 13 | uspb "github.com/jfeng45/servicetmpl1/applicationservice/userclient/generatedclient" 14 | "github.com/pkg/errors" 15 | "google.golang.org/grpc" 16 | ) 17 | 18 | const ( 19 | DEV_CONFIG string = "../../app/config/appConfigDev.yaml" 20 | PROD_CONFIG string = "../../app/config/appConfigProd.yaml" 21 | ) 22 | 23 | type UserService struct { 24 | //container container.Container 25 | container *servicecontainer.ServiceContainer 26 | } 27 | 28 | func catchPanic() { 29 | if p := recover(); p != nil { 30 | logger.Log.Errorf("%+v\n", p) 31 | } 32 | } 33 | 34 | func (uss *UserService) RegisterUser(ctx context.Context, req *uspb.RegisterUserReq) (*uspb.RegisterUserResp, error) { 35 | defer catchPanic() 36 | logger.Log.Debug("RegisterUser called") 37 | 38 | ruci, err := containerhelper.GetRegistrationUseCase(uss.container) 39 | if err != nil { 40 | logger.Log.Errorf("%+v\n", err) 41 | return nil, errors.Wrap(err, "") 42 | } 43 | mu, err := userclient.GrpcToUser(req.User) 44 | 45 | if err != nil { 46 | logger.Log.Errorf("%+v\n", err) 47 | return nil, errors.Wrap(err, "") 48 | } 49 | logger.Log.Debug("mu:", mu) 50 | resultUser, err := ruci.RegisterUser(mu) 51 | if err != nil { 52 | logger.Log.Errorf("%+v\n", err) 53 | return nil, errors.Wrap(err, "") 54 | } 55 | logger.Log.Debug("resultUser:", resultUser) 56 | gu, err := userclient.UserToGrpc(resultUser) 57 | if err != nil { 58 | logger.Log.Errorf("%+v\n", err) 59 | return nil, errors.Wrap(err, "") 60 | } 61 | 62 | logger.Log.Debug("user registered: ", gu) 63 | 64 | return &uspb.RegisterUserResp{User: gu}, nil 65 | 66 | } 67 | 68 | func (uss *UserService) ListUser(ctx context.Context, in *uspb.ListUserReq) (*uspb.ListUserResp, error) { 69 | defer catchPanic() 70 | logger.Log.Debug("ListUser called") 71 | 72 | luci, err := containerhelper.GetListUserUseCase(uss.container) 73 | if err != nil { 74 | logger.Log.Errorf("%+v\n", err) 75 | return nil, errors.Wrap(err, "") 76 | } 77 | 78 | lu, err := luci.ListUser() 79 | if err != nil { 80 | logger.Log.Errorf("%+v\n", err) 81 | return nil, errors.Wrap(err, "") 82 | } 83 | gu, err := userclient.UserListToGrpc(lu) 84 | if err != nil { 85 | logger.Log.Errorf("%+v\n", err) 86 | return nil, errors.Wrap(err, "") 87 | } 88 | 89 | logger.Log.Debug("user list: ", gu) 90 | 91 | return &uspb.ListUserResp{User: gu}, nil 92 | 93 | } 94 | func runServer(sc *servicecontainer.ServiceContainer) error { 95 | logger.Log.Debug("start runserver") 96 | 97 | srv := grpc.NewServer() 98 | 99 | cs := &UserService{sc} 100 | uspb.RegisterUserServiceServer(srv, cs) 101 | //l, err:=net.Listen(GRPC_NETWORK, GRPC_ADDRESS) 102 | ugc := sc.AppConfig.UserGrpcConfig 103 | logger.Log.Debugf("userGrpcConfig: %+v\n", ugc) 104 | l, err := net.Listen(ugc.DriverName, ugc.UrlAddress) 105 | if err != nil { 106 | return errors.Wrap(err, "") 107 | } else { 108 | logger.Log.Debug("server listening") 109 | } 110 | return srv.Serve(l) 111 | } 112 | 113 | func main() { 114 | filename := DEV_CONFIG 115 | //filename := PROD_CONFIG 116 | container, err := buildContainer(filename) 117 | if err != nil { 118 | fmt.Printf("%+v\n", err) 119 | //logger.Log.Errorf("%+v\n", err) 120 | panic(err) 121 | } 122 | if err := runServer(container); err != nil { 123 | logger.Log.Errorf("Failed to run user server: %+v\n", err) 124 | panic(err) 125 | } else { 126 | logger.Log.Info("server started") 127 | } 128 | } 129 | 130 | func buildContainer(filename string) (*servicecontainer.ServiceContainer, error) { 131 | 132 | container, err := app.InitApp(filename) 133 | sc := container.(*servicecontainer.ServiceContainer) 134 | if err != nil { 135 | //logger.Log.Errorf("%+v\n", err) 136 | return nil, errors.Wrap(err, "") 137 | } 138 | return sc, nil 139 | } 140 | -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jfeng45/servicetmpl1/app" 6 | "github.com/jfeng45/servicetmpl1/app/container" 7 | "github.com/jfeng45/servicetmpl1/app/container/containerhelper" 8 | "github.com/jfeng45/servicetmpl1/app/logger" 9 | "github.com/jfeng45/servicetmpl1/domain/model" 10 | "github.com/jfeng45/servicetmpl1/tool/timea" 11 | "github.com/pkg/errors" 12 | "time" 13 | ) 14 | 15 | const ( 16 | DEV_CONFIG string = "../app/config/appConfigDev.yaml" 17 | PROD_CONFIG string = "../app/config/appConfigProd.yaml" 18 | ) 19 | 20 | func main() { 21 | testMySql() 22 | //testCouchDB() 23 | } 24 | 25 | func testMySql() { 26 | 27 | filename := DEV_CONFIG 28 | container, err := buildContainer(filename) 29 | if err != nil { 30 | fmt.Printf("%+v\n", err) 31 | return 32 | } 33 | testListUser(container) 34 | testFindById(container) 35 | testRegisterUser(container) 36 | testModifyUser(container) 37 | testUnregister(container) 38 | testModifyAndUnregister(container) 39 | testModifyAndUnregisterWithTx(container) 40 | 41 | } 42 | func testCouchDB() { 43 | filename := PROD_CONFIG 44 | container, err := buildContainer(filename) 45 | if err != nil { 46 | fmt.Printf("%+v\n", err) 47 | //logger.Log.Errorf("%+v\n", err) 48 | return 49 | } 50 | testFindById(container) 51 | } 52 | 53 | func testUnregister(container container.Container) { 54 | 55 | ruci, err := containerhelper.GetRegistrationUseCase(container) 56 | if err != nil { 57 | logger.Log.Fatal("registration interface build failed:%+v\n", err) 58 | } 59 | username := "Aditi" 60 | err = ruci.UnregisterUser(username) 61 | if err != nil { 62 | logger.Log.Fatalf("testUnregister failed:%+v\n", err) 63 | } 64 | logger.Log.Infof("testUnregister successully") 65 | } 66 | 67 | func testRegisterUser(container container.Container) { 68 | ruci, err := containerhelper.GetRegistrationUseCase(container) 69 | if err != nil { 70 | logger.Log.Fatal("registration interface build failed:%+v\n", err) 71 | } 72 | created, err := time.Parse(timea.FORMAT_ISO8601_DATE, "2018-12-09") 73 | if err != nil { 74 | logger.Log.Errorf("date format err:%+v\n", err) 75 | } 76 | 77 | user := model.User{Name: "Brian", Department: "Marketing", Created: created} 78 | 79 | resultUser, err := ruci.RegisterUser(&user) 80 | if err != nil { 81 | logger.Log.Errorf("user registration failed:%+v\n", err) 82 | } else { 83 | logger.Log.Info("new user registered:", resultUser) 84 | } 85 | } 86 | 87 | func testModifyUser(container container.Container) { 88 | ruci, err := containerhelper.GetRegistrationUseCase(container) 89 | if err != nil { 90 | logger.Log.Fatal("registration interface build failed:%+v\n", err) 91 | } 92 | created, err := time.Parse(timea.FORMAT_ISO8601_DATE, "2019-12-01") 93 | if err != nil { 94 | logger.Log.Errorf("date format err:%+v\n", err) 95 | } 96 | user := model.User{Id: 29, Name: "Aditi", Department: "HR", Created: created} 97 | err = ruci.ModifyUser(&user) 98 | if err != nil { 99 | logger.Log.Infof("Modify user failed:%+v\n", err) 100 | } else { 101 | logger.Log.Info("user modified succeed:", user) 102 | } 103 | } 104 | 105 | func testListUser(container container.Container) { 106 | rluf, err := containerhelper.GetListUserUseCase(container) 107 | if err != nil { 108 | logger.Log.Fatal("RetrieveListUser interface build failed:", err) 109 | } 110 | users, err := rluf.ListUser() 111 | if err != nil { 112 | logger.Log.Errorf("user list failed:%+v\n", err) 113 | } 114 | logger.Log.Info("user list:", users) 115 | } 116 | 117 | func testModifyAndUnregister(container container.Container) { 118 | ruci, err := containerhelper.GetRegistrationUseCase(container) 119 | if err != nil { 120 | logger.Log.Fatal("RegisterRegistration interface build failed:%+v\n", err) 121 | } 122 | created, err := time.Parse(timea.FORMAT_ISO8601_DATE, "2018-12-09") 123 | if err != nil { 124 | logger.Log.Errorf("date format err:%+v\n", err) 125 | } 126 | user := model.User{Id: 31, Name: "Richard", Department: "Sales", Created: created} 127 | err = ruci.ModifyAndUnregister(&user) 128 | if err != nil { 129 | logger.Log.Errorf("ModifyAndUnregister failed:%+v\n", err) 130 | } else { 131 | logger.Log.Infof("ModifyAndUnregister succeed") 132 | } 133 | } 134 | 135 | func testModifyAndUnregisterWithTx(container container.Container) { 136 | rtuci, err := containerhelper.GetRegistrationTxUseCase(container) 137 | if err != nil { 138 | logger.Log.Fatal("RegisterRegistration interface build failed:%+v\n", err) 139 | } 140 | created, err := time.Parse(timea.FORMAT_ISO8601_DATE, "2018-12-09") 141 | if err != nil { 142 | logger.Log.Errorf("date format err:%+v\n", err) 143 | } 144 | user := model.User{Id: 32, Name: "Richard", Department: "Finance", Created: created} 145 | err = rtuci.ModifyAndUnregisterWithTx(&user) 146 | if err != nil { 147 | logger.Log.Errorf("ModifyAndUnregisterWithTx failed:%+v\n", err) 148 | } else { 149 | logger.Log.Infof("ModifyAndUnregisterWithTx succeed") 150 | } 151 | } 152 | 153 | func testFindById(container container.Container) { 154 | //It is uid in database. Make sure you have it in database, otherwise it won't find it. 155 | id := 10 156 | rluf, err := containerhelper.GetListUserUseCase(container) 157 | if err != nil { 158 | logger.Log.Fatalf("RetrieveListUser interface build failed:%+v\n", err) 159 | } 160 | user, err := rluf.Find(id) 161 | if err != nil { 162 | logger.Log.Errorf("fin user failed failed:%+v\n", err) 163 | } 164 | logger.Log.Info("find user:", user) 165 | } 166 | 167 | func buildContainer(filename string) (container.Container, error) { 168 | container, err := app.InitApp(filename) 169 | if err != nil { 170 | return nil, errors.Wrap(err, "") 171 | } 172 | return container, nil 173 | } 174 | -------------------------------------------------------------------------------- /domain/model/cache.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | //Cache is the domain model for cache service 4 | type Cache struct { 5 | key string 6 | value []byte 7 | } 8 | -------------------------------------------------------------------------------- /domain/model/course.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | // Course is created to show the project layout and courseDataServiceFactory, no real use. 4 | type Course struct { 5 | Id int 6 | Name string 7 | } 8 | -------------------------------------------------------------------------------- /domain/model/user.go: -------------------------------------------------------------------------------- 1 | // Package model represents domain model. Every domain model type should have it's own file. 2 | // It shouldn't depends on any other package in the application. 3 | // It should only has domain model type and limited domain logic, in this example, validation logic. Because all other 4 | // package depends on this package, the import of this package should be as small as possible. 5 | 6 | package model 7 | 8 | import ( 9 | "github.com/go-ozzo/ozzo-validation" 10 | "time" 11 | ) 12 | 13 | // User has a name, department and created date. Name and created are required, department is optional. 14 | // Id is auto-generated by database after the user is persisted. 15 | // json is for couchdb 16 | type User struct { 17 | Id int `json:"uid"` 18 | Name string `json:"username"` 19 | Department string `json:"department"` 20 | Created time.Time `json:"created"` 21 | } 22 | 23 | // Validate validates a newly created user, which has not persisted to database yet, so Id is empty 24 | func (u User) Validate() error { 25 | return validation.ValidateStruct(&u, 26 | validation.Field(&u.Name, validation.Required), 27 | validation.Field(&u.Created, validation.Required)) 28 | } 29 | 30 | //ValidatePersisted validate a user that has been persisted to database, basically Id is not empty 31 | func (u User) ValidatePersisted() error { 32 | return validation.ValidateStruct(&u, 33 | validation.Field(&u.Id, validation.Required), 34 | validation.Field(&u.Name, validation.Required), 35 | validation.Field(&u.Created, validation.Required)) 36 | } 37 | -------------------------------------------------------------------------------- /domain/usecase/listuser/listUser.go: -------------------------------------------------------------------------------- 1 | // Package registration represents the concrete implementation of ListUserUseCaseInterface interface 2 | package listuser 3 | 4 | import ( 5 | "github.com/jfeng45/servicetmpl1/app/logger" 6 | "github.com/jfeng45/servicetmpl1/applicationservice/dataservice" 7 | "github.com/jfeng45/servicetmpl1/domain/model" 8 | "github.com/pkg/errors" 9 | "strconv" 10 | ) 11 | 12 | // ListUserUseCase implements ListUseCaseInterface. 13 | type ListUserUseCase struct { 14 | // UserDataInterface, which is a interface to underline database connection and can be used to access 15 | // persistence layer 16 | UserDataInterface dataservice.UserDataInterface 17 | // CacheDataInterface, which is a interface to outside gRPC cache service and can be used to access gRPC service 18 | CacheDataInterface dataservice.CacheDataInterface 19 | } 20 | 21 | func (luc *ListUserUseCase) ListUser() ([]model.User, error) { 22 | return luc.UserDataInterface.FindAll() 23 | } 24 | func (luc *ListUserUseCase) Find(id int) (*model.User, error) { 25 | users, err := luc.getFromCache(strconv.Itoa(id)) 26 | if err != nil { 27 | //not found in cache and continue 28 | logger.Log.Errorf("get from cache error:", err) 29 | //return nil, errors.Wrap(err, "") 30 | } 31 | if users != nil { 32 | //here should return the results from cache, however, right now the cache doesn't store user info, 33 | //so, just call find(id). This is not real code. Please replace it with real code 34 | return luc.UserDataInterface.Find(id) 35 | } 36 | return luc.UserDataInterface.Find(id) 37 | } 38 | 39 | // GetFromCache is a fake function to just show a call to outside service, the call to outside is working, 40 | // but the results returned is not right 41 | func (luc *ListUserUseCase) getFromCache(key string) ([]model.User, error) { 42 | value, err := luc.CacheDataInterface.Get(key) 43 | if err != nil { 44 | return nil, errors.Wrap(err, "") 45 | } 46 | logger.Log.Info("value from get cache: ", value) 47 | return nil, nil 48 | } 49 | -------------------------------------------------------------------------------- /domain/usecase/registration/registrationHelper.go: -------------------------------------------------------------------------------- 1 | package registration 2 | 3 | import ( 4 | "github.com/jfeng45/servicetmpl1/applicationservice/dataservice" 5 | "github.com/jfeng45/servicetmpl1/domain/model" 6 | "github.com/pkg/errors" 7 | "strconv" 8 | ) 9 | 10 | func modifyUser(udi dataservice.UserDataInterface, user *model.User) error { 11 | //loggera.Log.Debug("modifyUser") 12 | err := user.ValidatePersisted() 13 | if err != nil { 14 | return errors.Wrap(err, "user validation failed") 15 | } 16 | rowsAffected, err := udi.Update(user) 17 | if err != nil { 18 | return errors.Wrap(err, "") 19 | } 20 | if rowsAffected != 1 { 21 | return errors.New("Modify user failed. rows affected is " + strconv.Itoa(int(rowsAffected))) 22 | } 23 | return nil 24 | } 25 | 26 | func unregisterUser(udi dataservice.UserDataInterface, username string) error { 27 | affected, err := udi.Remove(username) 28 | if err != nil { 29 | return errors.Wrap(err, "") 30 | } 31 | if affected == 0 { 32 | errStr := "UnregisterUser failed. No such user " + username 33 | return errors.New(errStr) 34 | } 35 | 36 | if affected != 1 { 37 | errStr := "UnregisterUser failed. Number of users unregistered are " + strconv.Itoa(int(affected)) 38 | return errors.New(errStr) 39 | } 40 | return nil 41 | } 42 | 43 | // The business function will be wrapped inside a transaction or a non-transaction function 44 | // It needs to be written in a way that every error will be returned so it can be caught by TxEnd() function, 45 | // which will handle commit and rollback 46 | func ModifyAndUnregister(udi dataservice.UserDataInterface, user *model.User) error { 47 | //loggera.Log.Debug("ModifyAndUnregister") 48 | err := modifyUser(udi, user) 49 | if err != nil { 50 | return errors.Wrap(err, "") 51 | } 52 | err = unregisterUser(udi, user.Name) 53 | if err != nil { 54 | return errors.Wrap(err, "") 55 | } 56 | return nil 57 | } 58 | -------------------------------------------------------------------------------- /domain/usecase/registration/registraton.go: -------------------------------------------------------------------------------- 1 | // Package registrationTx represents the concrete implementation of RegistrationUseCaseInterface and 2 | // RegistrationTxUseCaseInterface interface. 3 | // Because the same business function can be created to support both transaction and non-transaction, 4 | // a shared business function is created in a helper file, then we can wrap that function with transaction 5 | // or non-transaction. 6 | package registration 7 | 8 | import ( 9 | "github.com/jfeng45/servicetmpl1/applicationservice/dataservice" 10 | "github.com/jfeng45/servicetmpl1/domain/model" 11 | "github.com/pkg/errors" 12 | ) 13 | 14 | // RegistrationUseCase implements RegistrationUseCaseInterface. 15 | // It has UserDataInterface, which can be used to access persistence layer 16 | // TxDataInterface is needed to support transaction 17 | type RegistrationUseCase struct { 18 | UserDataInterface dataservice.UserDataInterface 19 | } 20 | 21 | func (ruc *RegistrationUseCase) RegisterUser(user *model.User) (*model.User, error) { 22 | err := user.Validate() 23 | if err != nil { 24 | return nil, errors.Wrap(err, "user validation failed") 25 | } 26 | isDup, err := ruc.isDuplicate(user.Name) 27 | if err != nil { 28 | return nil, errors.Wrap(err, "") 29 | } 30 | if isDup { 31 | return nil, errors.New("duplicate user for " + user.Name) 32 | } 33 | resultUser, err := ruc.UserDataInterface.Insert(user) 34 | 35 | if err != nil { 36 | return nil, errors.Wrap(err, "") 37 | } 38 | return resultUser, nil 39 | } 40 | 41 | func (ruc *RegistrationUseCase) ModifyUser(user *model.User) error { 42 | return modifyUser(ruc.UserDataInterface, user) 43 | } 44 | 45 | func (ruc *RegistrationUseCase) isDuplicate(name string) (bool, error) { 46 | user, err := ruc.UserDataInterface.FindByName(name) 47 | //logger.Log.Debug("isDuplicate() user:", user) 48 | if err != nil { 49 | return false, errors.Wrap(err, "") 50 | } 51 | if user != nil { 52 | return true, nil 53 | } 54 | return false, nil 55 | } 56 | 57 | func (ruc *RegistrationUseCase) UnregisterUser(username string) error { 58 | return unregisterUser(ruc.UserDataInterface, username) 59 | } 60 | 61 | // The use case of ModifyAndUnregister without transaction 62 | func (ruc *RegistrationUseCase) ModifyAndUnregister(user *model.User) error { 63 | return ModifyAndUnregister(ruc.UserDataInterface, user) 64 | } 65 | 66 | -------------------------------------------------------------------------------- /domain/usecase/registration/registratonTx.go: -------------------------------------------------------------------------------- 1 | package registration 2 | 3 | import ( 4 | "github.com/jfeng45/servicetmpl1/applicationservice/dataservice" 5 | "github.com/jfeng45/servicetmpl1/domain/model" 6 | ) 7 | 8 | // RegistrationTxUseCase implements RegistrationTxUseCaseInterface. 9 | // It has UserDataInterface, which can be used to access persistence layer 10 | type RegistrationTxUseCase struct { 11 | UserDataInterface dataservice.UserDataInterface 12 | } 13 | 14 | // The use case of ModifyAndUnregister with transaction 15 | func (rtuc *RegistrationTxUseCase) ModifyAndUnregisterWithTx(user *model.User) error { 16 | 17 | udi := rtuc.UserDataInterface 18 | return udi.EnableTx(func() error { 19 | // wrap the business function inside the TxEnd function 20 | return ModifyAndUnregister(udi, user) 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /domain/usecase/useCase.go: -------------------------------------------------------------------------------- 1 | // Package usecase defines all the interfaces for a Micro-service application. 2 | // It is the entry point for the application's business logic. It is a top level package for a Micro-service application. 3 | // This top level package only defines interface, the concrete implementations are defined in sub-package of it. 4 | // It only depends on model package. No other package should dependent on it except cmd. 5 | 6 | // If transaction is supported, the transaction boundary should be defined in this package. 7 | // A suffix-"WithTx" can be added to the name of a transaction function to distinguish it from a non-transaction one. 8 | 9 | // The use cases in this application are not real world use cases, and they are created to illustrate the project layout. 10 | // You should replace them with your real use case. 11 | 12 | package usecase 13 | 14 | import ( 15 | "github.com/jfeng45/servicetmpl1/domain/model" 16 | ) 17 | 18 | // RegistrationUseCaseInterface is for users to register themselves to an application. It has registration related functions. 19 | // ModifyAndUnregisterWithTx() is the one supporting transaction, the other are not. 20 | type RegistrationUseCaseInterface interface { 21 | // RegisterUser register a user to an application, basically save it to a database. The returned resultUser that has 22 | // a Id ( auto generated by database) after persisted 23 | RegisterUser(user *model.User) (resultUser *model.User, err error) 24 | // UnregisterUser unregister a user from an application by user name, basically removing it from a database. 25 | UnregisterUser(username string) error 26 | // ModifyUser change user information based on the User.Id passed in. 27 | ModifyUser(user *model.User) error 28 | // ModifyAndUnregister change user information and then unregister the user based on the User.Id passed in. 29 | // It is created to illustrate transaction, no real use. 30 | ModifyAndUnregister(user *model.User) error 31 | } 32 | 33 | // RegistrationUseTxCaseInterface is for use case with transaction. It has registration related functions. 34 | type RegistrationTxUseCaseInterface interface { 35 | // ModifyAndUnregisterWithTx changes user information and then unregisters the user based on the User.Id passed in. 36 | // It supports transaction 37 | // It is created to illustrate transaction, no real life business logic behind it. 38 | ModifyAndUnregisterWithTx(user *model.User) error 39 | } 40 | 41 | // ListUserUseCaseInterface handles different ways to retrieve user information 42 | type ListUserUseCaseInterface interface { 43 | // ListUser retrieves all users as an array of user 44 | ListUser() ([]model.User, error) 45 | // Find retrieves a user based on a user's id 46 | Find(id int) (*model.User, error) 47 | } 48 | 49 | // ListCourseUseCaseInterface handles different ways to retrieve user information 50 | // It is created to show POC of courseDatServiceFactory, no real use 51 | // Only SQL database is implemented for this use case, not couchdb. 52 | type ListCourseUseCaseInterface interface { 53 | // ListCourse retrieves all courses as an array of course 54 | ListCourse() ([]model.Course, error) 55 | } 56 | 57 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/jfeng45/servicetmpl1 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 // indirect 7 | github.com/flimzy/diff v0.1.7 // indirect 8 | github.com/flimzy/kivik v1.8.1 // indirect 9 | github.com/flimzy/testy v0.1.17 // indirect 10 | github.com/go-kivik/couchdb v1.8.1 11 | github.com/go-kivik/kivik v1.8.1 12 | github.com/go-ozzo/ozzo-validation v3.5.0+incompatible 13 | github.com/go-sql-driver/mysql v1.5.0 14 | github.com/golang/protobuf v1.3.1 15 | github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 // indirect 16 | github.com/imdario/mergo v0.3.9 // indirect 17 | github.com/jfeng45/glogger v0.0.0-20200604022340-2e2251ddc0a7 18 | github.com/jfeng45/gtransaction v0.0.0-20200601024905-babe8d366eed 19 | github.com/pkg/errors v0.9.1 20 | github.com/sirupsen/logrus v1.4.2 21 | go.uber.org/multierr v1.5.0 // indirect 22 | go.uber.org/zap v1.13.0 23 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859 24 | google.golang.org/grpc v1.21.1 25 | gopkg.in/yaml.v2 v2.2.2 26 | ) 27 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= 3 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 4 | github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 h1:zV3ejI06GQ59hwDQAvmK1qxOQGB3WuVTRoY0okPTAv0= 5 | github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= 6 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 9 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/flimzy/diff v0.1.7 h1:DRbd+lN3lY1xVuQrfqvDNsqBwA6RMbClMs6tS5sqWWk= 11 | github.com/flimzy/diff v0.1.7/go.mod h1:lFJtC7SPsK0EroDmGTSrdtWKAxOk3rO+q+e04LL05Hs= 12 | github.com/flimzy/kivik v1.8.1 h1:URl7e0OnfSvAu3ZHQ5BkvzRZlCmyYuDyWUCcPWIHlU0= 13 | github.com/flimzy/kivik v1.8.1/go.mod h1:S2aPycbG0eDFll4wgXt9uacSNkXISPufutnc9sv+mdA= 14 | github.com/flimzy/testy v0.1.17 h1:Y+TUugY6s4B/vrOEPo6SUKafc41W5aiX3qUWvhAPMdI= 15 | github.com/flimzy/testy v0.1.17/go.mod h1:3szguN8NXqgq9bt9Gu8TQVj698PJWmyx/VY1frwwKrM= 16 | github.com/go-kivik/couchdb v1.8.1 h1:2yjmysS48JYpyWTkx2E3c7ASZP8Kh0eABWnkKlV8bbw= 17 | github.com/go-kivik/couchdb v1.8.1/go.mod h1:5XJRkAMpBlEVA4q0ktIZjUPYBjoBmRoiWvwUBzP3BOQ= 18 | github.com/go-kivik/kivik v1.8.1 h1:GScP1mS5wP2km2awszvKzPEjC21lYjQGr3GY+4a/o2U= 19 | github.com/go-kivik/kivik v1.8.1/go.mod h1:nIuJ8z4ikBrVUSk3Ua8NoDqYKULPNjuddjqRvlSUyyQ= 20 | github.com/go-ozzo/ozzo-validation v3.5.0+incompatible h1:sUy/in/P6askYr16XJgTKq/0SZhiWsdg4WZGaLsGQkM= 21 | github.com/go-ozzo/ozzo-validation v3.5.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU= 22 | github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= 23 | github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 24 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= 25 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 26 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 27 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 28 | github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= 29 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 30 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 31 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 32 | github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 h1:l5lAOZEym3oK3SQ2HBHWsJUfbNBiTXJDeW2QDxw9AQ0= 33 | github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 34 | github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg= 35 | github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 36 | github.com/jfeng45/glogger v0.0.0-20200411145929-153280a3d7ac h1:nsCqQmVlSmyYwPmlGvQc1sINsfg2S/QY9KOzvkwJ59w= 37 | github.com/jfeng45/glogger v0.0.0-20200411145929-153280a3d7ac/go.mod h1:sf7BgRKEE81JPwRPpmXBmy04V2QEBahAvEi9jrV98vw= 38 | github.com/jfeng45/glogger v0.0.0-20200604022340-2e2251ddc0a7 h1:cSwWfnVNPioi9kn2SmwcMKsSOXzPOwKBHANuaFL+EV4= 39 | github.com/jfeng45/glogger v0.0.0-20200604022340-2e2251ddc0a7/go.mod h1:sf7BgRKEE81JPwRPpmXBmy04V2QEBahAvEi9jrV98vw= 40 | github.com/jfeng45/gtransaction v0.0.0-20200420133858-567c4b2e9a4d h1:ZY8v31Usa06kYBBpHZbyp7kSd/M8MXvmiQdjCR0fZrg= 41 | github.com/jfeng45/gtransaction v0.0.0-20200420133858-567c4b2e9a4d/go.mod h1:mxKpYvSQYKkzao3xy1HyMJC39TqrP/JqVH+nj1PULwY= 42 | github.com/jfeng45/gtransaction v0.0.0-20200531060401-3beeb5e86790 h1:QpPu3lUfYVXAxgY2anR6QpAETgH+L0FopI2l5ncoRZ4= 43 | github.com/jfeng45/gtransaction v0.0.0-20200531060401-3beeb5e86790/go.mod h1:mxKpYvSQYKkzao3xy1HyMJC39TqrP/JqVH+nj1PULwY= 44 | github.com/jfeng45/gtransaction v0.0.0-20200601024905-babe8d366eed h1:gipdFRSJuw90IWWViHvhJuat3vPER+7+fAlHp7o68g8= 45 | github.com/jfeng45/gtransaction v0.0.0-20200601024905-babe8d366eed/go.mod h1:mxKpYvSQYKkzao3xy1HyMJC39TqrP/JqVH+nj1PULwY= 46 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 47 | github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= 48 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 49 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 50 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 51 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 52 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 53 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 54 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 55 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 56 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 57 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 58 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 59 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 60 | github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= 61 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 62 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 63 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 64 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 65 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 66 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 67 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 68 | go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 69 | go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= 70 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 71 | go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= 72 | go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= 73 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= 74 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= 75 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= 76 | go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= 77 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 78 | go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU= 79 | go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= 80 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 81 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 82 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 83 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= 84 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 85 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 86 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 87 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 88 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= 89 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 90 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 91 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 92 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 93 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 94 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 95 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= 96 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 97 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 98 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 99 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 100 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 101 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 102 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs= 103 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 104 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 105 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 106 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= 107 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 108 | google.golang.org/grpc v1.21.1 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8= 109 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 110 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 111 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 112 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 113 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 114 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 115 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 116 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 117 | honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= 118 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 119 | -------------------------------------------------------------------------------- /script/user.sql: -------------------------------------------------------------------------------- 1 | /* 2 | SQLyog Ultimate 3 | MySQL - 5.0.96-community-log : Database - fasp 4 | ********************************************************************* 5 | */ 6 | 7 | CREATE DATABASE `service_config` ; 8 | 9 | /*Table structure for table `userinfo` */ 10 | 11 | DROP TABLE IF EXISTS `userinfo`; 12 | 13 | CREATE TABLE `userinfo` ( 14 | `uid` int(10) NOT NULL auto_increment, 15 | `username` varchar(64) default NULL, 16 | `department` varchar(64) default NULL, 17 | `created` date default NULL, 18 | PRIMARY KEY (`uid`) 19 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; 20 | 21 | CREATE TABLE `course` ( 22 | `id` INT(10) NOT NULL AUTO_INCREMENT, 23 | `name` VARCHAR(64) DEFAULT NULL, 24 | PRIMARY KEY (`id`) 25 | ) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; 26 | 27 | -------------------------------------------------------------------------------- /tool/doc.go: -------------------------------------------------------------------------------- 1 | // Package tool represents a wrapper to third party library. It may enhance or modify some of the functionalities 2 | // of a third party library or create a interface for it. 3 | 4 | package tool 5 | -------------------------------------------------------------------------------- /tool/timea/timea.go: -------------------------------------------------------------------------------- 1 | package timea 2 | 3 | // The following constants represents different data format layout 4 | const ( 5 | FORMAT_ISO8601_DATE = "2006-01-02" 6 | FORMAT_ISO8601_DATE_TIME = "2006-01-02 15:04:05" 7 | FORMAT_ISO8601_DATE_TIME_MILLI = "2006-01-02 15:04:05.000" 8 | FORMAT_ISO8601_DATE_TIME_MILLI_ZONE = "2006-01-02 15:04:05.000Z07:00" 9 | FORMAT_ISO8601_DATE_TIME_MICRO = "2006-01-02 15:04:05.000000" 10 | FORMAT_ISO8601_DATE_TIME_MICRO_ZONE = "2006-01-02 15:04:05.000000Z07:00" 11 | FORMAT_ISO8601_DATE_TIME_NANO = "2006-01-02 15:04:05.000000000" 12 | FORMAT_ISO8601_DATE_TIME_NANO_ZONE = "2006-01-02 15:04:05.00000000007:00" 13 | ) 14 | --------------------------------------------------------------------------------