{"id":20795895,"url":"https://github.com/g41797/sputnik","last_synced_at":"2025-08-12T22:40:16.398Z","repository":{"id":176690561,"uuid":"658439584","full_name":"g41797/sputnik","owner":"g41797","description":"sputnik is tiny golang framework for building of satellite/sidecar processes.","archived":false,"fork":false,"pushed_at":"2024-03-05T09:18:06.000Z","size":1606,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-01-18T11:24:56.153Z","etag":null,"topics":["adapter","asynchronous","block-is-actor","bridge","go","message-oriented","modular-monolith","satellite","sidecar","silverbullet","tiny-library"],"latest_commit_sha":null,"homepage":"https://pkg.go.dev/github.com/g41797/sputnik","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/g41797.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-06-25T18:40:12.000Z","updated_at":"2024-05-21T05:39:18.000Z","dependencies_parsed_at":null,"dependency_job_id":"a9376041-24a1-43e9-8cbd-fd09fd8ec93c","html_url":"https://github.com/g41797/sputnik","commit_stats":null,"previous_names":["g41797/sputnik"],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/g41797%2Fsputnik","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/g41797%2Fsputnik/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/g41797%2Fsputnik/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/g41797%2Fsputnik/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/g41797","download_url":"https://codeload.github.com/g41797/sputnik/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243141265,"owners_count":20242817,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["adapter","asynchronous","block-is-actor","bridge","go","message-oriented","modular-monolith","satellite","sidecar","silverbullet","tiny-library"],"created_at":"2024-11-17T16:24:34.511Z","updated_at":"2025-03-12T01:41:08.165Z","avatar_url":"https://github.com/g41797.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"![](_logo/logo.png)\n\n# “Everything should be made as simple as possible...” \n\n**sputnik** is tiny golang framework for building of **satellite** or as it's now fashionable to say **sidecar** processes.\n\n \n[![GoDev](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go\u0026logoColor=white)](https://pkg.go.dev/github.com/g41797/sputnik)[![Go](https://github.com/g41797/sputnik/actions/workflows/go.yml/badge.svg)](https://github.com/g41797/sputnik/actions/workflows/go.yml) [![Wiki](https://img.shields.io/badge/Wikipedia-%23000000.svg?style=for-the-badge\u0026logo=wikipedia\u0026logoColor=white)](https://en.wikipedia.org/wiki/Sputnik_1)\n \n\n##  What do satellite processes have in common?\nThe same sometimes boring flow:\n* Initialize process in deterministic order\n* Connect to the server process\n  * Periodically validate used connection\n  * Inform about failed connection and reconnect\n* Graceful shutdown\n  * Cleanup resources in deterministic order\n\n\nUsually such processes are used as **adapters**,**bridges** and/or **proxies** for server process - they *translate* foreign protocol to protocol of the server. \n \nDevelopers also want flexible way to create such processes without changing the code:\n  * All adapters in one process\n  * Adapter per process\n  * Other variants\n\nAnd it would be nice to write in CV that you developed **modular-monolith**. \n\n\n##  sputnik to the rescue.\nsputnik simplifies creation of satellite/sidecar  processes for servers.\n\n### Modular monolith\n\nsputnik forces modular-monolith design:\n  - process created as set of independent asynchronously running **Blocks**\n\n### Satellites blueprint\n\nsputnik supports common for all satellite processes functionality:\n* Deterministic initialization\n* Connect/Reconnect flow\n* Server heartbeat\n* Convenient negotiation between blocks of the process\n* Graceful shutdown\n\nAll this with minimal code size - actually the size of README and tests far exceeds the size of sputnik's code.\n\n## Why Sputnik?\n* Launched by the Soviet Union on 4 October 1957, **Sputnik** became the first **satellite** in space and changed the world forever.\n* Main mindset of Sputnik design was - simplicity and reliability that could be adapted to future projects\n* We were both born the same year but I'm a bit older\n\n\n## Less You Know, the Better You Sleep\nsputnik doesn't use any server information and only assumes that *server configuration* and *server connection*\nare required for functioning. \n\n### Server Configuration\n```go\ntype ServerConfiguration any\n```\n\nIn order to get configuration and provide it to the process, sputnik uses *Configuration Factory*:\n```go\ntype ConfFactory func(confName string, result any) error\n```\nwhere \n- confName - name of configuration\n- result - unmarshaled configuration(usually struct) \n\n\nThis function should be supplied by caller of sputnik during initialization. We will talk about initialization later.\n\n### Server Connection\n```go\ntype ServerConnection any\n```\n\nFor implementation of connect/disconnect/server health flow, sputnik uses supplied by caller implementation of following interface:\n```go\ntype ServerConnector interface {\n\t// Connects to the server and return connection to server\n\t// If connection failed, returns error.\n\t// ' Connect' for already connected\n\t// and still not brocken connection should\n\t// return the same value returned in previous\n\t// successful call(s) and nil error\n\tConnect(cf ConfFactory) (conn ServerConnection, err error)\n\n\t// Returns false if\n\t//  - was not connected at all\n\t//  - was connected, but connection is brocken\n\t// True returned if\n\t//  - connected and connection is alive\n\tIsConnected() bool\n\n\t// If connection is alive closes it\n\tDisconnect()\n}\n```\n\n### Messages\nsputnik supports asynchronous communication between Blocks of the process.\n```go\ntype Msg map[string]any\n```\n\nPossible types of the message:\n* command\n* query\n* event\n* update\n* .......\n\nDevelopers of blocks should agree on content of messages.\n\nsputnik doesn't force specific format of the message.\n\nEXCEPTION: key of the map should not start from \"__\".\n\nThis prefix is used by sputnik for house-keeping values.\n\n\n## sputnik's building blocks\nsputnik based process consists of *infrastructure* and *application* **Blocks**\n\n\n### Eats own dog food\nInfrastructure **Blocks**:\n  * *initiator* - dispatcher of all blocks\n  * *finisher*  - listener of external shutdown/exit events\n  * *connector* - connects/reconnects with server, provides this\n    information to another blocks\n\n\n### Block identity\nEvery Block has descriptor:\n```go\ntype BlockDescriptor struct {\n\tName           string\n\tResponsibility string\n}\n```\n\n**Name** of the Block should be unique. It is used for creation of the Block.\n\nGood Block names:\n* syslogreceiver\n* syslogpublisher\n* restprocessor\n\nBad Block names:\n* receiver\n* processor\n\nRemember - sputnik based process may support number of protocol adapters. And *receiver* usually is part of everyone.\n\n*Responsibility* of the Block is used for negotiation between blocks. It's possible to create the same block with different responsibilities.\n\n\n### Block interface\n\n**Block** has set of callbacks/hooks:\n* Mandatory:\n  * Init\n  * Finish\n  * Run\n* Optional\n  * OnServerConnect\n  * OnServerDisconnect\n  * OnMsg\n\nYou can see that these callbacks reflect life cycle/flow of satellite process.  \n\n#### Init\n\n```go\ntype Init func(cf ConfFactory) error\n```\n\nInit callback is executed by sputnik once during initialization.\nBlocks are initialized in *sequenced order* according to configuration.\n\nRules of initialization:\n * don't run hard processing within Init\n * don't work with server, wait *OnServerConnect*\n\nIf initialization failed (returned error != nil)\n * initialization is terminated\n * already initialized blocks are finished in opposite to init order.\n\n#### Run\n\n```go\ntype Run func(communicator BlockCommunicator)\n```\n\nRun callback is executed by sputnik\n* after successful initialization of ALL blocks\n* on own goroutine\n\nYou can consider Run as *main thread* of the block.\n\nParameter of Run - **BlockCommunicator** may be used by block for negotiation with another blocks of the process.\n\n#### Finish\n\n```go\ntype Finish func(init bool)\n```\n\nFinish callback is executed by sputnik twice:\n* during initialization of the process, if Init of another block failed (**init == true**)\n  * for this case Finish is called synchronously, on the thread(goroutine) of initialization\n* during shutdown of the process \n  * for this case Finish is called asynchronously on own goroutine\n\nFor any case, during Finish block\n* should clean all resources\n* stop all go-routines (don't forget Run's goroutine)\n\nAfter finish of all blocks Sputnik quits.\n\n#### OnServerConnect\n\n```go\ntype OnServerConnect func(connection any)\n```\n\n*Optional* OnServerConnect callback is executed by sputnik\n* after start of *Run*\n* after successful connection to server\n* on own goroutine\n\n\n#### OnServerDisconnect\n\n```go\ntype OnServerDisconnect func()\n```\n*Optional* OnServerDisconnect callback is executed by sputnik\n* after start of *Run*\n* when previously connected server disconnects\n* on own goroutine\n\n#### OnMsg\n\n```go\ntype OnMsg func(msg Msg)\n```\n*Optional*  OnMsg callback is executed by sputnik \n* after start of *Run*\n* as result of receiving Msg from another block\n* Block also can send message to itself\n\n**UNLIKE OTHER CALLBACKS, OnMsg CALLED SEQUENTIALLY ONE BY ONE FROM THE SAME DEDICATED GOROUTINE**. Frankly speaking - you have the queue of messages.\n\n### Block creation\n\nDeveloper supplies *BlockFactory* function:\n```go\ntype BlockFactory func() *Block\n```\n*BlockFactory* registered in the process via RegisterBlockFactory:\n```go\nfunc RegisterBlockFactory(blockName string, blockFactory BlockFactory)\n```\nUse *init()* for this registration:\n```go\nfunc init() { // Registration of finisher block factory\n\tRegisterBlockFactory(DefaultFinisherName, finisherBlockFactory)\n}\n```\n\nWhere finisherBlockFactory is :\n```go\nfunc finisherBlockFactory() *Block {\n\tfinisher := new(finisher)\n\tblock := NewBlock(\n\t\tWithInit(finisher.init),\n\t\tWithRun(finisher.run),\n\t\tWithFinish(finisher.finish),\n\t\tWithOnMsg(finisher.debug))\n\treturn block\n}\n```\nYou can see that factory called *NewBlock function* using [functional options pattern](https://golang.cafe/blog/golang-functional-options-pattern.html):\n\nList of options:\n```go\n// Mandatory:\nWithInit(f Init)\nWithFinish(f Finish)\nWithRun(f Run)\n\n// Optional:\nWithOnConnect(f OnServerConnect)\nWithOnDisconnect(f OnServerDisconnect)\nWithOnMsg(f OnMsg)\n```\nwhere *f* is related callback/hook\n\n### Block control\nBlock control is provided via interface *BlockCommunicator*. Block gets own communicator as parameter of **Run**.\n```go\ntype BlockCommunicator interface {\n\t//\n\t// Get communicator of block by block's responsibility\n\t// Example - get BlockCommunicator of initiator:\n\t// initbl, ok := bc.Communicator(sputnik.InitiatorResponsibility)\n\t//\n\tCommunicator(resp string) (bc BlockCommunicator, exists bool)\n\n\t// Identification of controlled block\n\tDescriptor() BlockDescriptor\n\n\t// Asynchronously send message to controlled block\n\t// true is returned if\n\t//  - controlled block has OnMsg callback\n\t//  - recipient of messages was not cancelled\n\t//  - msg != nil\n\tSend(msg Msg) bool\n}\n```\nMain usage of own BlockCommunicator:\n* get BlockCommunicator of another block\n* send message to this block\n\nExample: *initiator* sends setup settings to *connector*:\n```go\n\tsetupMsg := make(Msg)\n\tsetupMsg[\"__connector\"] = connectorPlugin\n\tsetupMsg[\"__timeout\"] = 10000\n\n\tconnCommunicator.Send(setupMsg)\n```\n\n## sputnik flight\n\n### Create sputnik\n\nUse *NewSputnik function* for creation of sputnik.\nIt supports following options:\n```go\nWithConfFactory(cf ConfFactory)                      // Set Configuration Factory. Mandatory\nWithAppBlocks(appBlocks []BlockDescriptor)           // List of descriptors for application blocks\nWithBlockFactories(blkFacts BlockFactories)          // List of block factories. Optional. If was not set, used list of factories registrated during init()\nWithFinisher(fbd BlockDescriptor)                    // Descriptor of finisher. Optional. If was not set, default supplied finished will be used.\nWithConnector(cnt ServerConnector, to time.Duration) // Server Connector plug-in and timeout for connect/reconnect. Optional\n```\n\nExample: creation of sputnik for tests:\n```go\n\ttestSputnik, _ := sputnik.NewSputnik(\n\t\tsputnik.WithConfFactory(dumbConf),\n\t\tsputnik.WithAppBlocks(blkList),\n\t\tsputnik.WithBlockFactories(tb.factories()),\n\t\tsputnik.WithConnector(\u0026tb.conntr, tb.to),\n\t)\n```\n\n### Preparing for flight\n\nAfter creation of sputnik, call *Prepare*:\n```go\n// Creates and initializes all blocks.\n//\n// If creation and initialization of any block failed:\n//\n//   - Finish is called on all already initialized blocks\n//\n//   - Order of finish - reversal of initialization\n//\n//     = Returned error describes reason of the failure\n//\n// Otherwise returned 2 functions for sputnik management:\n//\n//   - lfn - Launch of the sputnik , exit from this function will be\n//     after signal for shutdown of the process  or after call of\n//     second returned function (see below)\n//\n//   - st - ShootDown of sputnik - abort flight\nfunc (sputnik Sputnik) Prepare() (lfn Launch, st ShootDown, err error) \n```\n\nExample :\n```go\n\tlaunch, kill, err := testSputnik.Prepare()\n```\n\n### sputnik launch\n\nVery simple - just call returned *launch* function.\n\nThis call is synchronous. sputnik continues to run on current goroutine till\n* process termination (it means that launch may be last line in main)\n* call of second returned function\n\nIn order to use kill(ShootDown of sputnik) function, launch and kill should run\non different go-routines.\n\n## Adding blocks to the build\n\nFor adding blocks to the build use **blank imports**:\n```go\nimport (\n\t// Attach blocks packages to the process:\n\t_ \"github.com/memphisdev/memphis-protocol-adapter/pkg/syslogblocks\"\n)\n```\n## Never Asked Questions\n\n- [I'd like to create stand alone process without any server. Is it possible?](#id-like-to-create-stand-alone-process-without-any-server-is-it-possible)\n- [I'd like to embed sputnik to my process? Is it possible?](#id-like-to-embed-sputnik-to-my-process-is-it-possible)\n- [You wrote that *finisher* can be replaced. For what?](#you-wrote-that-finisher-can-be-replaced-for-what)\n\n### I'd like to create stand alone process without any server? Is it possible?\n\nDon't use *WithConnector* option and sputnik will not run any server connector.\nBut for this case your blocks should not have  *OnServerConnect* and *OnServerDisconnect* callbacks.\n\n### I'd like to embed sputnik to my process? Is it possible?\n\nOf course, supply *ServerConnector* for in-proc communication.\n\n### You wrote that *finisher* can be replaced. For what?\n\nFor example in case above, you will need to coordinate exit with code of the host.\n\n## Contributing\n\nFeel free to report bugs and suggest improvements.\n\n## License\n\n[MIT](LICENSE)\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fg41797%2Fsputnik","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fg41797%2Fsputnik","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fg41797%2Fsputnik/lists"}