github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/docs/reference/README.md (about) 1 --- 2 title: Reference 3 keywords: micro 4 tags: [micro] 5 sidebar: home_sidebar 6 permalink: /reference 7 summary: Reference - a comprehensive guide to Micro 8 --- 9 10 ## Reference 11 {: .no_toc } 12 13 This reference doc is an in depth guide for the technical details and usage of Micro 14 15 ## Contents 16 {: .no_toc } 17 18 * TOC 19 {:toc} 20 21 ## Overview 22 23 Micro is a platform for cloud native development. It consists of a server, command line interface and 24 service framework which enables you to build, run, manage and consume Micro services. This reference 25 walks through the majority of Micro in depth and attempts to help guide you through any usage. It 26 should be thought of much like a language spec and will evolve over time. 27 28 ## Installation 29 30 Below are the instructions for installing micro locally, in docker or on kubernetes 31 32 ### Local 33 34 Micro can be installed locally in the following way. We assume for the most part a Linux env with Go and Git installed. 35 36 #### Go Get 37 38 ``` 39 go get github.com/tickoalcantara12/micro/v3 40 ``` 41 42 #### Docker 43 44 ```sh 45 docker pull ghcr.io/micro/micro:latest 46 ``` 47 48 #### Release Binaries 49 50 ```sh 51 # MacOS 52 curl -fsSL https://raw.githubusercontent.com/micro/micro/master/scripts/install.sh | /bin/bash 53 54 # Linux 55 wget -q https://raw.githubusercontent.com/micro/micro/master/scripts/install.sh -O - | /bin/bash 56 57 # Windows 58 powershell -Command "iwr -useb https://raw.githubusercontent.com/micro/micro/master/scripts/install.ps1 | iex" 59 ``` 60 61 ### Kubernetes 62 63 Micro can be installed onto a Kubernetes cluster using helm. Micro will be deployed in full and leverage zero-dep implementations designed for Kubernetes. For example, micro store will internally leverage a file store on a persistent volume, meaning there are no infrastructure dependencies required. 64 65 #### Dependencies 66 67 You will need to be connected to a Kubernetes cluster 68 69 #### Install 70 71 Install micro with the following commands: 72 73 ```shell 74 helm repo add micro https://micro.github.io/helm 75 helm install micro micro/micro 76 ``` 77 78 #### Uninstall 79 80 Uninstall micro with the following commands: 81 82 ```shell 83 helm uninstall micro 84 helm repo remove micro 85 ``` 86 87 ## Server 88 89 The micro server is a distributed systems runtime for the Cloud and beyond. It provides the building 90 blocks for distributed systems development as a set of services, command line and service framework. 91 The server is much like a distributed operating system in the sense that each component runs 92 independent of each other but work together as one system. This composition allows us to use a 93 microservices architecture pattern even for the platform. 94 95 ### Features 96 97 The server provides the below functionality as built in primitives for services development. 98 99 - Authentication 100 - Configuration 101 - PubSub Messaging 102 - Event Streaming 103 - Service Discovery 104 - Service Networking 105 - Key-Value Storage 106 - HTTP API Gateway 107 - gRPC Identity Proxy 108 - Web Dashboard 109 110 ### Usage 111 112 To start the server simply run 113 114 ```sh 115 micro server 116 ``` 117 118 In docker 119 120 ``` 121 sudo docker run -p 8080:8080 -p 8081:8081 ghcr.io/micro/micro:latest server 122 ``` 123 124 This will boot the entire system and services including a http api on :8080 and grpc proxy on :8081 125 126 ### Help 127 128 Run the following command to check help output 129 ``` 130 micro --help 131 ``` 132 133 ### Environment 134 135 Environments define where the server is running, by default this should be local 136 137 Check with the following command 138 139 ``` 140 micro env 141 ``` 142 143 To set the environment do 144 145 ``` 146 micro env set local 147 ``` 148 149 ### Login 150 151 Before starting login using the default user `admin` with password `micro` 152 153 ``` 154 micro login 155 ``` 156 157 ### Commands 158 159 Here's a quick list of useful commands 160 161 ``` 162 micro --help # execute help to list commands 163 micro env # show the environment config 164 micro login # login to the server 165 micro services # check what's running 166 micro status # check service status 167 ``` 168 169 ## Start helloworld 170 171 Run helloworld and check its status 172 173 ``` 174 # check env is set to local 175 micro env 176 # run the helloworld service 177 micro run github.com/micro/services/helloworld 178 # check the service status to see it's running 179 micro status 180 # once running should be listed in services 181 micro services 182 ``` 183 184 Call the service and verify output 185 186 ```sh 187 $ micro helloworld --name=Alice 188 { 189 "msg": "Hello Alice" 190 } 191 ``` 192 193 Curl it from the API 194 195 ``` 196 curl "http://localhost:8080/helloworld/Call?name=Alice" 197 ``` 198 199 Stop the service 200 201 ``` 202 micro kill helloworld 203 ``` 204 205 ## Command Line 206 207 The command line interface is the primary way to interact with a micro server. It's a simple binary that 208 can either be interacted with using simple commands or an interactive prompt. The CLI proxies all commands 209 as RPC calls to the Micro server. In many of the builtin commands it will perform formatting and additional 210 syntactic work. 211 212 ### Builtin Commands 213 214 Built in commands are system or configuration level commands for interacting with the server or 215 changing user config. For the most part this is syntactic sugar for user convenience. Here's a 216 subset of well known commands. 217 218 ``` 219 signup 220 login 221 run 222 update 223 kill 224 services 225 logs 226 status 227 env 228 user 229 ``` 230 231 The micro binary and each subcommand has a --help flag to provide a usage guide. The majority should be 232 obvious to the user. We will go through a few in more detail. 233 234 <!-- 235 236 #### Signup 237 238 Signup is a command which attempts to query a "signup" to register a new account, this is env specific and requires a signup service to be 239 running. By default locally this will not exist and we expect the user to use the admin/micro credentials to administer the system. 240 You can then choose to run your own signup service conforming to the proto in micro/proto or use `micro auth create account`. 241 242 Signup is seen as a command for those who want to run their own micro server for others and potentially license the software to take payment. 243 ---> 244 245 #### Login 246 247 Login authenticates the user and stores credentials locally in a .micro/tokens file. This calls the micro auth service to authenticate the 248 user against existing accounts stored in the system. Login asks for a username and password at the prompt. 249 250 ### Dynamic Commands 251 252 When issuing a command to the Micro CLI (ie. `micro command`), if the command is not a builtin, Micro will try to dynamically resolve this command and call 253 a service running. Let's take the `micro registry` command, because although the registry is a core service that's running by default on a local Micro setup, 254 the `registry` command is not a builtin one. 255 256 With the `--help` flag, we can get information about available subcommands and flags 257 258 ```sh 259 $ micro registry --help 260 NAME: 261 micro registry 262 263 VERSION: 264 latest 265 266 USAGE: 267 micro registry [command] 268 269 COMMANDS: 270 deregister 271 getService 272 listServices 273 register 274 watch 275 ``` 276 277 The commands listed are endpoints of the `registry` service (see `micro services`). 278 279 To see the flags (which are essentially endpoint request parameters) for a subcommand: 280 281 ```sh 282 $ micro registry getService --help 283 NAME: 284 micro registry getService 285 286 USAGE: 287 micro registry getService [flags] 288 289 FLAGS: 290 --service string 291 --options_ttl int64 292 --options_domain string 293 294 ``` 295 296 At this point it is useful to have a look at the proto of the [registry service here](https://github.com/tickoalcantara12/micro/blob/master/proto/registry/registry.proto). 297 298 In particular, let's see the `GetService` endpoint definition to understand how request parameters map to flags: 299 300 ```proto 301 message Options { 302 int64 ttl = 1; 303 string domain = 2; 304 } 305 306 message GetRequest { 307 string service = 1; 308 Options options = 2; 309 } 310 ``` 311 312 As the above definition tells us, the request of `GetService` has the field `service` at the top level, and fields `ttl` and `domain` in an options structure. 313 The dynamic CLI maps the underscored flagnames (ie. `options_domain`) to request fields, so the following request JSON: 314 315 ```js 316 { 317 "service": "serviceName", 318 "options": { 319 "domain": "domainExample" 320 } 321 } 322 ``` 323 324 is equivalent to the following flags: 325 326 ```sh 327 micro registry getService --service=serviceName --options_domain=domainExample 328 ``` 329 330 ### User Config 331 332 The command line uses local user config stores in ~/.micro for any form of state such as saved environments, 333 tokens, etc. It will always attempt to read from here unless specified otherwise. Currently we store all 334 config in a single file `config.json` and any auth tokens in a `tokens` file. 335 336 ## Environments 337 338 Micro is built with a federated and multi-environment model in mind. Our development normally maps through local, staging and production, so Micro takes 339 this forward looking view and builds in the notion of environments which are completely isolated micro environments you can interact with through the CLI. 340 This reference explains environments. 341 342 ### View Current 343 344 Environments can be displayed using the `micro env` command. 345 346 ```sh 347 $ micro env 348 * local 127.0.0.1:8081 349 dev proxy.m3o.dev 350 platform proxy.m3o.com 351 ``` 352 353 There are three builtin environments, `local` being the default, and two [`m3o` specific](m3o.com) offerings; dev and platform. 354 These exist for convenience and speed of development. Additional environments can be created using `micro env add [name] [host:port]`. 355 Environment addresses point to the micro proxy which defaults to :8081. 356 357 ### Add Environment 358 359 The command `micro env --help` provides a summary of usage. Here's an example of how to add an environment. 360 361 ```sh 362 $ micro env add foobar example.com 363 $ micro env 364 * local 127.0.0.1:8081 365 dev proxy.m3o.dev 366 platform proxy.m3o.com 367 foobar example.com 368 ``` 369 370 ### Set Environment 371 372 The `*` marks which environment is selected. Let's select the newly added: 373 374 ```sh 375 $ micro env set foobar 376 $ micro env 377 local 127.0.0.1:8081 378 dev proxy.m3o.dev 379 platform proxy.m3o.com 380 * foobar example.com 381 ``` 382 383 ### Login to an Environment 384 385 Each environment is effectively an isolated deployment with its own authentication, storage, etc. So each env requires signup and login. At this point we have to log in to the `example` env with `micro login`. If you don't have credentials to the environment, you have to ask the admin. 386 387 ### Web Dashboard 388 389 View and query services in a web browser at localhost:8082. The web dashboard is a simple 390 layer on top of the system to visualise services and their endpoints. Additionally it generates 391 dynamic forms for easy querying. 392 393 Run the dashboard with the command 394 ``` 395 micro web 396 ``` 397 398 ## Services 399 400 Micro is built as a distributed operating system leveraging the microservices architecture pattern. 401 402 ### Overview 403 404 Below we describe the list of services provided by the Micro Server. Each service is considered a 405 building block primitive for a platform and distributed systems development. The proto 406 interfaces for each can be found in [micro/proto/auth](https://github.com/tickoalcantara12/micro/blob/master/proto/auth/auth.proto) 407 and the Go library, client and server implementations in [micro/service/auth](https://github.com/tickoalcantara12/micro/tree/master/service/auth). 408 409 ### API 410 411 The API service is a http API gateway which acts as a public entrypoint and converts http/json to RPC. 412 413 #### Overview 414 415 The micro API is the public entrypoint for all external access to services to be consumed by frontend, mobile, etc. The api 416 accepts http/json requests and uses path based routing to resolve to backend services. It converts the request to gRPC and 417 forward appropriately. The idea here is to focus on microservices on the backend and stitch everything together as a single 418 API for the frontend. 419 420 #### Usage 421 422 In the default `local` [environment](#environments) the API address is `127.0.0.1:8080`. 423 Each service running is callable through this API. 424 425 ```sh 426 $ curl http://127.0.0.1:8080/ 427 {"version": "v3.0.0-beta"} 428 ``` 429 430 An example call would be listing services in the registry: 431 432 ```sh 433 $ curl http://127.0.0.1:8080/registry/listServices 434 ``` 435 436 The format is 437 ``` 438 curl http://127.0.0.1:8080/[servicename]/[endpointName] 439 ``` 440 441 The endpoint name is lower camelcase. 442 443 The parameters can be passed on as query params 444 445 ```sh 446 $ curl http://127.0.0.1:8080/helloworld/call?name=Joe 447 {"msg":"Hello Joe"} 448 ``` 449 450 or JSON body: 451 452 ```sh 453 curl -XPOST --header "Content-Type: application/json" -d '{"name":"Joe"}' http://127.0.0.1:8080/helloworld/call 454 {"msg":"Hello Joe"} 455 ``` 456 457 To specify a namespace when calling the API, the `Micro-Namespace` header can be used: 458 459 ```sh 460 $ curl -H "Micro-Namespace: foobar" http://127.0.0.1:8080/helloworld/call?name=Joe 461 ``` 462 463 To call a [non-public service/endpoint](#auth), the `Authorization` header can be used: 464 465 ```sh 466 MICRO_API_TOKEN=`micro user token` 467 curl -H "Authorization: Bearer $MICRO_API_TOKEN" http://127.0.0.1:8080/helloworld/call?name=Joe 468 ``` 469 470 ### Auth 471 472 The auth service provides both authentication and authorization. 473 474 #### Overview 475 476 The auth service stores accounts and access rules. It provides the single source of truth for all authentication 477 and authorization within the Micro runtime. Every service and user requires an account to operate. When a service 478 is started by the runtime an account is generated for it. Core services and services run by Micro load rules 479 periodically and manage the access to their resources on a per request basis. 480 481 #### Usage 482 483 For CLI command help use `micro auth --help` or auth subcommand help such as `micro auth create --help`. 484 485 #### Login 486 487 To login to a server simply so the following 488 489 ``` 490 $ micro login 491 Enter username: admin 492 Enter password: 493 Successfully logged in. 494 ``` 495 496 Assuming you are pointing to the right environment. It defaults to the local `micro server`. 497 498 #### Rules 499 500 Rules determine what resource a user can access. The default rule is the following: 501 502 ```sh 503 $ micro auth list rules 504 ID Scope Access Resource Priority 505 default <public> GRANTED *:*:* 0 506 ``` 507 508 The `default` rule makes all services callable that appear in the `micro status` output. 509 Let's see an example of this. 510 511 ```sh 512 $ micro run helloworld 513 # Wait for the service to accept calls 514 $ curl 127.0.0.1:8080/helloworld/call?name=Alice 515 {"msg":"Hello Alice"} 516 ``` 517 518 If we want to prevent unauthorized users from calling our services, we can create the following rule 519 520 ```sh 521 # This command creates a rule that enables only logged in users to call the micro server 522 micro auth create rule --access=granted --scope='*' --resource="*:*:*" onlyloggedin 523 # Create the rule which allows us to login 524 micro auth create rule --access=granted --resource="service:auth:*" auth-public 525 ``` 526 527 and delete the default one. 528 Here, the scope `*` is markedly different from the `<public>` scope we have seen earlier when doing a `micro auth list rules`: 529 530 ```sh 531 $ micro auth list rules 532 ID Scope Access Resource Priority 533 auth-public <public> GRANTED service:auth:* 0 534 onlyloggedin * GRANTED *:*:* 0 535 default <public> GRANTED *:*:* 0 536 ``` 537 538 Now, let's remove the default rule. 539 540 ```sh 541 # This command deletes the 'default' rule - the rule which enables anyone to call the 'micro server'. 542 $ micro auth delete rule default 543 Rule deleted 544 ``` 545 546 Let's try curling our service again: 547 548 ```sh 549 $ curl 127.0.0.1:8080/helloworld/call?name=Alice 550 {"Id":"helloworld","Code":401,"Detail":"Unauthorized call made to helloworld:Helloworld.Call","Status":"Unauthorized"} 551 ``` 552 553 Our `onlyloggedin` rule took effect. We can still call the service with a token: 554 555 ```sh 556 $ token=$(micro user token) 557 # Locally: 558 # curl "Authorization: Bearer $token" 127.0.0.1:8080/helloworld/call?name=Alice 559 {"msg":"Hello Alice"} 560 ``` 561 562 (Please note tokens have a limited lifetime so the line `$ token=$(micro user token)` has to be reissued from time to time, or the command must be used inline.) 563 564 #### Accounts 565 566 Auth service supports the concept of accounts. The default account used to access the `micro server` is the admin account. 567 568 ```sh 569 $ micro auth list accounts 570 ID Name Scopes Metadata 571 admin admin admin n/a 572 ``` 573 574 We can create accounts for teammates and coworkers with `micro auth create account`: 575 576 ```sh 577 $ micro auth create account --scopes=admin jane 578 Account created: {"id":"jane","type":"","issuer":"micro","metadata":null,"scopes":["admin"],"secret":"bb7c1a96-c0c6-4ff5-a0e9-13d456f3db0a","name":"jane"} 579 ``` 580 581 The freshly created account can be used with `micro login` by using the `jane` id and `bb7c1a96-c0c6-4ff5-a0e9-13d456f3db0a` password. 582 583 ### Broker 584 585 The broker is a message broker for asynchronous pubsub messaging. 586 587 #### Overview 588 589 The broker provides a simple abstraction for pubsub messaging. It focuses on simple semantics for fire-and-forget 590 asynchronous communication. The goal here is to provide a pattern for async notifications where some update or 591 events occurred but that does not require persistence. The client and server build in the ability to publish 592 on one side and subscribe on the other. The broker provides no message ordering guarantees. 593 594 While a Service is normally called by name, messaging focuses on Topics that can have multiple publishers and 595 subscribers. The broker is abstracting away in the service's client/server which includes message encoding/decoding 596 so you don't have to spend all your time marshalling. 597 598 ##### Client 599 600 The client contains the `Publish` method which takes a proto message, encodes it and publishes onto the broker 601 on a given topic. It takes the metadata from the client context and includes these as headers in the message 602 including the content-type so the subscribe side knows how to deal with it. 603 604 ##### Server 605 606 The server supports a `Subscribe` method which allows you to register a handler as you would for handling requests. 607 In this way we can mirror the handler behaviour and deserialize the message when consuming from the broker. In 608 this model the server handles connecting to the broker, subscribing, consuming and executing your subscriber 609 function. 610 611 #### Usage 612 613 Publisher: 614 ```go 615 bytes, err := json.Marshal(&Healthcheck{ 616 Healthy: true, 617 Service: "foo", 618 }) 619 if err != nil { 620 return err 621 } 622 623 return broker.Publish("health", &broker.Message{Body: bytes}) 624 ``` 625 626 Subscriber: 627 ```go 628 handler := func(msg *broker.Message) error { 629 var hc Healthcheck 630 if err := json.Unmarshal(msg.Body, &hc); err != nil { 631 return err 632 } 633 634 if hc.Healthy { 635 logger.Infof("Service %v is healthy", hc.Service) 636 } else { 637 logger.Infof("Service %v is not healthy", hc.Service) 638 } 639 640 return nil 641 } 642 643 sub, err := broker.Subscribe("health", handler) 644 if err != nil { 645 return err 646 } 647 ``` 648 649 ### Config 650 651 The config service provides dynamic configuration for services. 652 653 #### Overview 654 655 Config can be stored and loaded separately to 656 the application itself for configuring business logic, api keys, etc. We read and write these as key-value 657 pairs which also support nesting of JSON values. The config interface also supports storing secrets by 658 defining the secret key as an option at the time of writing the value. 659 660 #### Usage 661 662 Let's assume we have a service called `helloworld` from which we want to read configuration data. 663 First we have to insert said data with the cli. Config data can be organized under different "paths" with the dot notation. 664 It's a good convention to save all config data belonging to a service under a top level path segment matching the service name: 665 666 ```sh 667 $ micro config set helloworld.somekey hello 668 $ micro config get helloworld.somekey 669 hello 670 ``` 671 672 We can save another key too and read all values in one go with the dot notation: 673 674 ```sh 675 $ micro config set helloworld.someotherkey "Hi there!" 676 $ micro config get helloworld 677 {"somekey":"hello","someotherkey":"Hi there!"} 678 ``` 679 680 As it can be seen, the config (by default) stores configuration data as JSONs. 681 We can save any type: 682 683 ```sh 684 $ micro config set helloworld.someboolkey true 685 $ micro config get helloworld.someboolkey 686 true 687 $ micro config get helloworld 688 {"someboolkey":true,"somekey":"hello","someotherkey":"Hi there!"} 689 ``` 690 691 So far we have only saved top level keys. Let's explore the advantages of the dot notation. 692 693 ```sh 694 $ micro config set helloworld.keywithsubs.subkey1 "So easy!" 695 {"keywithsubs":{"subkey1":"So easy!"},"someboolkey":true,"somekey":"hello","someotherkey":"Hi there!"} 696 ``` 697 698 Some of the example keys are getting in our way, let's learn how to delete: 699 700 ```sh 701 $ micro config del helloworld.someotherkey 702 $ micro config get helloworld 703 {"keywithsubs":{"subkey1":"So easy!"},"someboolkey":true,"somekey":"hello"} 704 ``` 705 706 We can of course delete not just `leaf` level keys, but top level ones too: 707 708 ```sh 709 $ micro config del helloworld.keywithsubs 710 $ micro config get helloworld 711 {"someboolkey":true,"somekey":"hello"} 712 ``` 713 714 ##### Secrets 715 716 The config also supports secrets - values encrypted at rest. This helps in case of leaks, be it a security one or an accidental copy paste. 717 718 They are fairly easy to save: 719 720 ```sh 721 $ micro config set --secret helloworld.hushkey "Very secret stuff" 722 $ micro config get helloworld.hushkey 723 [secret] 724 725 $ micro config get --secret helloworld.hushkey 726 Very secret stuff 727 728 $ micro config get helloworld 729 {"hushkey":"[secret]","someboolkey":true,"somekey":"hello"} 730 731 $ micro config get --secret helloworld 732 {"hushkey":"Very secret stuff","someboolkey":true,"somekey":"hello"} 733 ``` 734 735 Even bool or number values can be saved as secrets, and they will appear as the string constant `[secret]` unless decrypted: 736 737 ```sh 738 $ micro config set --secret helloworld.hush_number_key 42 739 $ micro config get helloworld 740 {"hush_number_key":"[secret]","hushkey":"[secret]","someboolkey":true,"somekey":"hello"} 741 742 $ micro config get --secret helloworld 743 {"hush_number_key":42,"hushkey":"Very secret stuff","someboolkey":true,"somekey":"hello"} 744 ``` 745 746 #### Service Framework 747 748 It is similarly easy to access and set config values from a service. 749 A good example of reading values is [the config example test service](https://github.com/micro/services/blob/master/test/conf): 750 751 ```go 752 package main 753 754 import ( 755 "fmt" 756 "time" 757 758 "github.com/tickoalcantara12/micro/v3/service" 759 "github.com/tickoalcantara12/micro/v3/service/config" 760 ) 761 762 type keyConfig struct { 763 Subkey string `json:"subkey"` 764 Subkey1 int `json:"subkey1"` 765 } 766 767 type conf struct { 768 Key keyConfig `json:"key"` 769 } 770 771 func main() { 772 go func() { 773 for { 774 time.Sleep(time.Second) 775 val, err := config.Get("key.subkey") 776 fmt.Println("Value of key.subkey: ", val.String(""), err) 777 778 val, err = config.Get("key", config.Secret(true)) 779 if err != nil { 780 fmt.Println(err) 781 } 782 c := conf{} 783 err = val.Scan(&c.Key) 784 fmt.Println("Value of key.subkey1: ", c.Key.Subkey1, err) 785 } 786 }() 787 788 // run the service 789 service.Run() 790 } 791 ``` 792 793 The above service will print the value of `key.subkey` and `key.subkey` every second. 794 By passing in the `config.Secret(true)` option, we tell config to decrypt secret values for us, similarly to the `--secret` CLI flag. 795 796 The [config interface](https://github.com/tickoalcantara12/micro/blob/master/service/config/config.go) specifies not just `Get` `Set` and `Delete` to access values, 797 but a few convenience functions too in the `Value` interface. 798 799 It is worth noting that `String` `Int` etc methods will do a best effort try at coercing types, ie. if the value saved is a string, `Int` will try to parse it. 800 However, the same does not apply to the `Scan` method, which uses `json.Unmarshal` under the hood, which we all know fails when encountering type mismatches. 801 802 `Get` should, in all cases, return a non nil `Value`, so even if the `Get` errors, `Value.Int()` and other operations should never panic. 803 804 #### Advanced Concepts 805 806 ##### Merging Config Values 807 808 When saving a string with the CLI that is a valid JSON map, it gets expanded to be saved as a proper map structure, instead of a string, ie 809 810 ```sh 811 $ micro config set helloworld '{"a": "val1", "b": "val2"}' 812 $ micro config get helloworld.a 813 val1 814 # If the string would be saved as is, `helloworld.a` would be a nonexistent path 815 ``` 816 817 The advantages of this become particularly visible when `Set`ting a complex type with the library: 818 819 ```go 820 type conf struct { 821 A string `json:"a"` 822 B string `json:"b"` 823 } 824 825 c1 := conf{"val1", "val2"} 826 config.Set("key", c1) 827 828 v, _ := config.Get("key") 829 c2 := &conf{} 830 v.Scan(c2) 831 // c1 and c2 should be equal 832 ``` 833 834 Or with the following example 835 836 ```sh 837 $ micro config del helloworld 838 $ micro config set helloworld '{"a":1}' 839 $ micro config get helloworld 840 {"a":1} 841 $ micro config set helloworld '{"b":2}' 842 $ micro config get helloworld 843 {"a":1,"b":2} 844 ``` 845 846 #### Secret encryption keys for `micro server` 847 848 By default, if not specified, `micro server` generates and saves an encryption key to the location `~/.micro/config_secret_key`. This is intended for local zero dependency use, but not for production. 849 850 To specify the secret for the micro server either the env var `MICRO_CONFIG_SECRET_KEY` or the flag `config_secret_key` key must be specified. 851 852 ### Errors 853 854 The errors package provides error types for most common HTTP status codes, e.g. BadRequest, InternalServerError etc. It's recommended when returning an error to an RPC handler, one of these errors is used. If any other type of error is returned, it's treated as an InternalServerError. 855 856 Micro API detects these error types and will use them to determine the response status code. For example, if your handler returns errors.BadRequest, the API will return a 400 status code. If no error is returned the API will return the default 200 status code. 857 858 Error codes are also used when handling retries. If your service returns a 500 (InternalServerError) or 408 (Timeout) then the client will retry the request. Other status codes are treated as client error and won't be retried. 859 860 #### Usage 861 862 ```go 863 import ( 864 "github.com/tickoalcantara12/micro/v3/service/errors" 865 ) 866 867 func (u *Users) Read(ctx context.Context, req *pb.ReadRequest, rsp *pb.ReadResponse) error { 868 if len(req.Id) == 0 { 869 return errors.BadRequest("users.Read.MissingID", "Missing ID") 870 } 871 872 ... 873 } 874 ``` 875 876 ### Events 877 878 The events service is a service for event streaming and persistent storage of events. 879 880 #### Overview 881 882 Event streaming differs from pubsub messaging in that it provides an ordered stream of events that can be consumed 883 or replayed from any given point in the past. If you have experience with Kafka then you know it's basically a 884 distributed log which allows you to read a file from different offsets and stream it. 885 886 The event service and interface provide the event streaming abstraction for writing and reading events along with 887 consuming from any given offset. It also supports acking and error handling where appropriate. 888 889 Events also different from the broker in that it provides a fixed Event type where you fill in the details and 890 handle the decoding of the message body yourself. Events could have large payloads so we don't want to 891 unnecessarily decode where you may just want to hand off to a storage system. 892 893 #### Functions 894 895 The events package has two parts: Stream and Store. Stream is used to Publish and Consume to messages for a given topic. For example, in a chat application one user would Publish a message and another would subscribe. If you later needed to retrieve messages, you could either replay them using the Subscribe function and passing the Offset option, or list them using the Read function. 896 897 ```go 898 func Publish(topic string, msg interface{}, opts ...PublishOption) error 899 ``` 900 The Publish function has two required arguments: topic and message. Topic is the channel you're publishing the event to, in the case of a chat application this would be the chat id. The message is any struct, e.g. the message being sent to the chat. When the subscriber receives the event they'll be able to unmarshal this object. Publish has two supported options, WithMetadata to pass key/value pairs and WithTimestamp to override the default timestamp on the event. 901 902 ```go 903 func Consume(topic string, opts ...ConsumeOption) (<-chan Event, error) 904 ``` 905 The Consume function is used to consume events. In the case of a chat application, the client would pass the chat ID as the topic, and any events published to the stream will be sent to the event channel. Event has an Unmarshal function which can be used to access the message payload, as demonstrated below: 906 907 ```go 908 for { 909 evChan, err := events.Consume(chatID) 910 if err != nil { 911 logger.Error("Error subscribing to topic %v: %v", chatID, err) 912 return err 913 } 914 for { 915 ev, ok := <- evChan 916 if !ok { 917 break 918 } 919 var msg Message 920 if err :=ev.Unmarshal(&msg); err != nil { 921 logger.Errorf("Error unmarshaling event %v: %v", ev.ID, err) 922 return err 923 } 924 logger.Infof("Received message: %v", msg.Subject) 925 } 926 } 927 ``` 928 929 #### Example 930 931 The [Chat Service](https://github.com/micro/services/tree/master/test/chat) examples usage of the events service, leveraging both the stream and store functions. 932 933 ### Network 934 935 The network is a service to service network for request proxying 936 937 #### Overview 938 939 The network provides a service to service networking abstraction that includes proxying, authentication, 940 tenancy isolation and makes use of the existing service discovery and routing system. The goal here 941 is not to provide service mesh but a higher level control plane for routing that can govern access 942 based on the existing system. The network requires every service to be pointed to it, making 943 an explicit choice for routing. 944 945 Beneath the covers cilium, envoy and other service mesh tools can be used to provide a highly 946 resilient mesh. 947 948 ### Registry 949 950 The registry is a service directory and endpoint explorer 951 952 #### Overview 953 954 The service registry provides a single source of truth for all services and their APIs. All services 955 on startup will register their name, version, address and endpoints with the registry service. They 956 then periodically re-register to "heartbeat", otherwise being expired based on a pre-defined TTL 957 of 90 seconds. 958 959 The goal of the registry is to allow the user to explore APIs and services within a running system. 960 961 The simplest form of access is the below command to list services. 962 963 ``` 964 micro services 965 ``` 966 967 #### Usage 968 969 The get service endpoint returns information about a service including response parameters parameters for endpoints: 970 971 ```sh 972 $ micro registry getService --service=helloworld 973 { 974 "services": [ 975 { 976 "name": "helloworld", 977 "version": "latest", 978 "metadata": { 979 "domain": "micro" 980 }, 981 "endpoints": [ 982 { 983 "name": "Helloworld.Call", 984 "request": { 985 "name": "CallRequest", 986 "type": "CallRequest", 987 "values": [ 988 { 989 "name": "name", 990 "type": "string", 991 "values": [] 992 } 993 ] 994 }, 995 "response": { 996 "name": "CallResponse", 997 "type": "CallResponse", 998 "values": [ 999 { 1000 "name": "message", 1001 "type": "string", 1002 "values": [] 1003 } 1004 ] 1005 }, 1006 "metadata": {} 1007 }, 1008 { 1009 "name": "Helloworld.Stream", 1010 "request": { 1011 "name": "Context", 1012 "type": "Context", 1013 "values": [] 1014 }, 1015 "response": { 1016 "name": "Stream", 1017 "type": "Stream", 1018 "values": [] 1019 }, 1020 "metadata": { 1021 "stream": "true" 1022 } 1023 } 1024 ], 1025 "nodes": [ 1026 { 1027 "id": "helloworld-3a0d02be-f98e-4d9d-a8fa-24e942580848", 1028 "address": "192.168.43.193:34321", 1029 "port": "0", 1030 "metadata": { 1031 "broker": "service", 1032 "protocol": "grpc", 1033 "registry": "service", 1034 "server": "grpc", 1035 "transport": "grpc" 1036 } 1037 } 1038 ], 1039 "options": { 1040 "ttl": "0", 1041 "domain": "" 1042 } 1043 } 1044 ] 1045 } 1046 ``` 1047 1048 This is an especially useful feature for writing custom meta tools like API explorers. 1049 1050 ### Runtime 1051 1052 #### Overview 1053 1054 The runtime service is responsible for running, updating and deleting binaries or containers (depending on the platform - eg. binaries locally, pods on k8s etc) and their logs. 1055 1056 #### Running a service 1057 1058 The `micro run` command tells the runtime to run a service. The following are all valid examples: 1059 1060 ```sh 1061 micro run github.com/micro/services/helloworld 1062 micro run . # deploy local folder to your local micro server 1063 micro run ../path/to/folder # deploy local folder to your local micro server 1064 micro run helloworld # deploy latest version, translates to micro run github.com/micro/services/helloworld or your custom base url 1065 micro run helloworld@9342934e6180 # deploy certain version 1066 micro run helloworld@branchname # deploy certain branch 1067 micro run --name helloworld . 1068 ``` 1069 1070 #### Specifying Service Name 1071 1072 The service name is derived from the directory name of your application. In case you want to override this specify the `--name` flag. 1073 1074 ``` 1075 micro run --name helloworld github.com/myorg/helloworld/server 1076 ``` 1077 1078 #### Running a local folder 1079 1080 If the first parameter is an existing local folder, ie 1081 1082 ```sh 1083 micro run ./foobar 1084 ``` 1085 1086 Then the CLI will upload that folder to the runtime and the runtime runs that. 1087 1088 #### Running a git source 1089 1090 If the first parameter to `micro run` points to a git repository (be it on GitHub, GitLab, Bitbucket or any other provider), then the address gets sent to the runtime and the runtime downloads the code and runs it. 1091 1092 ##### Using references 1093 1094 References are the part of the first parameter passed to run after the `@` sign. It can either be a branch name (no reference means version `latest` which equals to master in git terminology) or a commit hash. 1095 1096 When branch names are passed in, the latest commit of the code will run. 1097 1098 #### Listing runtime objects 1099 1100 The `micro status` command lists all things running in the runtime: 1101 1102 ```sh 1103 $ micro status 1104 NAME VERSION SOURCE STATUS BUILD UPDATED METADATA 1105 helloworld latest github.com/micro/services/helloworld running n/a 20h43m45s ago owner=admin, group=micro 1106 ``` 1107 1108 The output includes the error if there is one. Commands like `micro kill`, `micro logs`, `micro update` accept the name returned by the `micro status` as the first parameter (and not the service name as that might differ). 1109 1110 #### Updating a service 1111 1112 The `micro update` command makes the runtime pull the latest commit in the branch and restarts the service. 1113 1114 In case of local code it requires not the runtime name (returned by `micro status`) but the local path. For commit hash deploys it just restarts the service. 1115 1116 Examples: `micro update helloworld`, `micro update helloworld@branch`, `micro update helloworld@commit`, `micro update ./helloworld`. 1117 1118 #### Deleting a service 1119 1120 The `micro kill` command removes a runtime object from the runtime. It accepts the name returned by `micro status`. 1121 1122 Examples: `micro kill helloworld`. 1123 1124 #### Logs 1125 1126 The `micro logs` command shows logs for a runtime object. It accepts the name returned by `micro status`. 1127 1128 The `-f` flag makes the command stream logs continuously. 1129 1130 Examples: `micro logs helloworld`, `micro logs -f helloworld`. 1131 1132 ### Store 1133 1134 Micro's store interface is for persistent key-value storage. 1135 1136 For a good beginner level doc on the Store, please see the [Getting started tutorial](/getting-started#storage). 1137 1138 #### Overview 1139 1140 Key-value stores that support ordering of keys can be used to build complex applications. 1141 Due to their very limited feature set, key-value stores generally scale easily and reliably, often linearly with the number of nodes added. 1142 1143 This scalability comes at the expense of inconvenience and mental overhead when writing business logic. For use cases where linear scalability is important, this trade-off is preferred. 1144 1145 #### Query by ID 1146 1147 Reading by ID is the archetypal job for key value stores. Storing data to enable this ID works just like in any other database: 1148 1149 ```sh 1150 # entries designed for querying "users by id" 1151 KEY VALUE 1152 id1 {"id":"id1", "name":"Jane", "class":"firstGrade", "avgScore": 98} 1153 id2 {"id":"id2", "name":"Alice","class":"secondGrade", "avgScore": 92} 1154 id3 {"id":"id3", "name":"Joe", "class":"secondGrade" "avgScore": 89} 1155 id4 {"id":"id4", "name":"Betty","class":"thirdGrade" "avgScore": 94} 1156 ``` 1157 1158 ```go 1159 import "github.com/tickoalcantara12/micro/v3/service/store" 1160 1161 records, err := store.Read("id1") 1162 if err != nil { 1163 fmt.Println("Error reading from store: ", err) 1164 } 1165 fmt.Println(records[0].Value) 1166 // Will output {"id":"id1", "name":"Jane", "class":"firstGrade", "avgScore": 98} 1167 ``` 1168 1169 Given this data structure, we can do two queries: 1170 1171 - reading a given key (get "id1", get "id2") 1172 - if the keys are ordered, we can ask for X number of entries after a key (get 3 entries after "id2") 1173 1174 Finding values in an ordered set is possibly the simplest task we can ask a database. 1175 The problem with the above data structure is that it's not very useful to ask "find me keys coming in the order after "id2". To enable other kinds of queries, the data must be saved with different keys. 1176 1177 In the case of the schoold students, let's say we wan't to list by class. To do this, having the query in mind, we can copy the data over to another table named after the query we want to do: 1178 1179 #### Query by Field Value Equality 1180 1181 ```sh 1182 # entries designed for querying "users by class" 1183 KEY VALUE 1184 firstGrade/id1 {"id":"id1", "name":"Jane", "class":"firstGrade", "avgScore": 98} 1185 secondGrade/id2 {"id":"id2", "name":"Alice","class":"secondGrade", "avgScore": 92} 1186 secondGrade/id3 {"id":"id3", "name":"Joe", "class":"secondGrade" "avgScore": 89} 1187 thirdGrade/id4 {"id":"id4", "name":"Betty","class":"thirdGrade" "avgScore": 94} 1188 ``` 1189 1190 1191 ```go 1192 import "github.com/tickoalcantara12/micro/v3/service/store" 1193 1194 records, err := store.Read("", store.Prefix("secondGrade")) 1195 if err != nil { 1196 fmt.Println("Error reading from store: ", err) 1197 } 1198 fmt.Println(records[0].Value) 1199 // Will output 1200 // secondGrade/id2 {"id":"id2", "name":"Alice","class":"secondGrade", "avgScore": 92} 1201 // secondGrade/id3 {"id":"id3", "name":"Joe", "class":"secondGrade" "avgScore": 89} 1202 ``` 1203 1204 Since the keys are ordered it is very trivial to get back let's say "all second graders". 1205 Key value stores which have their keys ordered support something similar to "key starts with/key has prefix" query. In the case of second graders, listing all records where the "keys start with `secondGrade`" will give us back all the second graders. 1206 1207 This query is basically a `field equals to` as we essentially did a `field class == secondGrade`. But we could also exploit the ordered nature of the keys to do a value comparison query, ie `field avgScores is less than 90` or `field AvgScores is between 90 and 95` etc., if we model our data appropriately: 1208 1209 #### Query for Field Value Ranges 1210 1211 ```sh 1212 # entries designed for querying "users by avgScore" 1213 KEY VALUE 1214 089/id3 {"id":"id3", "name":"Joe", "class":"secondGrade" "avgScore": 89} 1215 092/id2 {"id":"id2", "name":"Alice","class":"secondGrade", "avgScore": 92} 1216 094/id4 {"id":"id4", "name":"Betty","class":"thirdGrade" "avgScore": 94} 1217 098/id1 {"id":"id1", "name":"Jane", "class":"firstGrade", "avgScore": 98} 1218 ``` 1219 1220 It's worth remembering that the keys are strings, and that they are ordered lexicographically. For this reason when dealing with numbering values, we must make sure that they are prepended to the same length appropriately. 1221 1222 At the moment Micro's store does not support this kind of query, this example is only here to hint at future possibilities with the store. 1223 1224 #### Tables Usage 1225 1226 Micro services only have access to one Store table. This means all keys live in the same namespace and can collide. A very useful pattern is to separate the entries by their intended query pattern, ie taking the "users by id" and users by class records above: 1227 1228 ```sh 1229 KEY VALUE 1230 # entries designed for querying "users by id" 1231 usersById/id1 {"id":"id1", "name":"Jane", "class":"firstGrade", "avgScore": 98} 1232 usersById/id2 {"id":"id2", "name":"Alice","class":"secondGrade", "avgScore": 92} 1233 usersById/id3 {"id":"id3", "name":"Joe", "class":"secondGrade" "avgScore": 89} 1234 usersById/id4 {"id":"id4", "name":"Betty","class":"thirdGrade" "avgScore": 94} 1235 # entries designed for querying "users by class" 1236 usersByClass/firstGrade/id1 {"id":"id1", "name":"Jane", "class":"firstGrade", "avgScore": 98} 1237 usersByClass/secondGrade/id2 {"id":"id2", "name":"Alice","class":"secondGrade", "avgScore": 92} 1238 usersByClass/secondGrade/id3 {"id":"id3", "name":"Joe", "class":"secondGrade" "avgScore": 89} 1239 usersByClass/thirdGrade/id4 {"id":"id4", "name":"Betty","class":"thirdGrade" "avgScore": 94} 1240 ``` 1241 1242 Respective go examples this way become: 1243 1244 ```go 1245 import "github.com/tickoalcantara12/micro/v3/service/store" 1246 1247 const idPrefix = "usersById/" 1248 1249 records, err := store.Read(idPrefix + "id1") 1250 if err != nil { 1251 fmt.Println("Error reading from store: ", err) 1252 } 1253 fmt.Println(records[0].Value) 1254 // Will output {"id":"id1", "name":"Jane", "class":"firstGrade", "avgScore": 98} 1255 ``` 1256 1257 ```go 1258 import "github.com/tickoalcantara12/micro/v3/service/store" 1259 1260 const classPrefix = "usersByClass/" 1261 1262 records, err := store.Read("", store.Prefix(classPrefix + "secondGrade")) 1263 if err != nil { 1264 fmt.Println("Error reading from store: ", err) 1265 } 1266 fmt.Println(records[0].Value) 1267 // Will output 1268 // secondGrade/id2 {"id":"id2", "name":"Alice","class":"secondGrade", "avgScore": 92} 1269 // secondGrade/id3 {"id":"id3", "name":"Joe", "class":"secondGrade" "avgScore": 89} 1270 ``` 1271 1272 ### Metadata 1273 1274 Metadata / headers can be passed via the context in RPC calls. The context/metadata package allows services to get and set metadata in a context. The Micro API will add request headers into context, for example if the "Foobar" header is set on an API call to "localhost:8080/users/List", the users service can access this value as follows: 1275 1276 ```go 1277 import ( 1278 "context" 1279 "github.com/tickoalcantara12/micro/v3/service/context/metadata" 1280 ) 1281 1282 ... 1283 1284 func (u *Users) List(ctx context.Context, req *pb.ListRequest, rsp *pb.ListResponse) error { 1285 val, ok := metadata.Get(ctx, "Foobar") 1286 if !ok { 1287 return fmt.Errorf("Missing Foobar header") 1288 } 1289 1290 fmt.Println("Foobar header was set to: %v", val) 1291 return nil 1292 } 1293 ``` 1294 1295 Likewise, clients can set metadata in context using the metadata.Set function as follows: 1296 1297 ```go 1298 func (u *Users) List(ctx context.Context, req *pb.ListRequest, rsp *pb.ListResponse) error { 1299 newCtx := metadata.Set(ctx, "Foobar", "mycustomval") 1300 fRsp, err := u.foosrv.Call(newCtx, &foosrv.Request{}) 1301 ... 1302 } 1303 ``` 1304 1305 ## Plugins 1306 1307 Micro is a pluggable architecture built on Go's interface types. Plugins enable swapping out underlying infrastructure. 1308 1309 ### Overview 1310 1311 Micro is pluggable, meaning the implementation for each module can be replaced depending on the requirements. Plugins are applied to the micro server and not to services directly, this is done so the underlying infrastructure can change with zero code changes required in your services. 1312 1313 An example of a pluggable interface is the store. Locally micro will use a filestore to persist data, this is great because it requires zero dependencies and still offers persistence between restarts. When running micro in a test suite, this could be swapped to an in-memory cache which is better suited as it offers consistency between runs. In production, this can be swapped out for standalone infrastructure such as cockroachdb or etcd depending on the requirement. 1314 1315 Let's take an example where our service wants to load data from the store. Our service would call `store.Read(userPrefix + userID)` to load the value, behind the scenes this will execute an RPC to the store service which will in-turn call `store.Read` on the current `DefaultStore` implementation configured for the server. 1316 1317 ### Profiles 1318 1319 Profiles are used to configure multiple plugins at once. Micro comes with a few profiles out the box, such as "local", "kubernetes" and "test". These profiles can be found in `profile/profile.go`. You can configure micro to use one of these profiles using the `MICRO_PROFILE` env var, for example: `MICRO_PROFILE=test micro server`. The default profile used is "local". 1320 1321 ### Writing a profile 1322 1323 Profiles should be created as packages within the profile directory. Let's create a "staging" profile by creating `profile/staging/staging.go`. The example below shows how to override the default store of `Local` profile to use an in-memory implementation: 1324 1325 ```go 1326 // Package staging configures micro for a staging environment 1327 package staging 1328 1329 import ( 1330 "github.com/urfave/cli/v2" 1331 1332 "github.com/tickoalcantara12/micro/v3/profile" 1333 "github.com/tickoalcantara12/micro/v3/service/store" 1334 "github.com/tickoalcantara12/micro/v3/service/store/memory" 1335 ) 1336 1337 func init() { 1338 profile.Register("staging", staging) 1339 } 1340 1341 var staging = &profile.Profile{ 1342 Name: "staging", 1343 Setup: func(ctx *cli.Context) error { 1344 profile.Local.Setup(ctx) 1345 store.DefaultStore = memory.NewStore() 1346 return nil 1347 }, 1348 } 1349 ``` 1350 1351 ```bash 1352 pushd profile/staging 1353 go mod init github.com/tickoalcantara12/micro/profile/staging 1354 go mod tidy 1355 popd 1356 ``` 1357 1358 ### Using a custom profile 1359 1360 You can load a custom profile using a couple of commands, the first adds a replace to your go mod, indicating it should look for your custom profile within the profile directory: 1361 1362 ```bash 1363 go mod edit -replace github.com/tickoalcantara12/micro/profile/staging/v3=./profile/staging 1364 go mod tidy 1365 ``` 1366 1367 The second command creates a profile.go file which imports your profile. When your profile is imported, the init() function which is defined in staging.go is called, registering your profile. 1368 1369 ``` 1370 micro init --profile=staging --output=profile.go 1371 ``` 1372 1373 Now you can start your server using this profile: 1374 ``` 1375 MICRO_PROFILE=staging go run . server 1376 ``` 1377