{"id":17650605,"url":"https://github.com/wjayesh/dapr-distributed-calendar","last_synced_at":"2025-08-11T20:13:21.987Z","repository":{"id":54684582,"uuid":"312474905","full_name":"wjayesh/dapr-distributed-calendar","owner":"wjayesh","description":null,"archived":false,"fork":false,"pushed_at":"2021-02-04T05:27:31.000Z","size":5965,"stargazers_count":5,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-07-29T01:26:15.964Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wjayesh.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-11-13T04:41:39.000Z","updated_at":"2025-02-02T15:24:25.000Z","dependencies_parsed_at":"2022-08-14T00:00:31.345Z","dependency_job_id":null,"html_url":"https://github.com/wjayesh/dapr-distributed-calendar","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/wjayesh/dapr-distributed-calendar","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wjayesh%2Fdapr-distributed-calendar","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wjayesh%2Fdapr-distributed-calendar/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wjayesh%2Fdapr-distributed-calendar/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wjayesh%2Fdapr-distributed-calendar/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wjayesh","download_url":"https://codeload.github.com/wjayesh/dapr-distributed-calendar/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wjayesh%2Fdapr-distributed-calendar/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":269948859,"owners_count":24501821,"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","status":"online","status_checked_at":"2025-08-11T02:00:10.019Z","response_time":75,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":[],"created_at":"2024-10-23T11:38:55.892Z","updated_at":"2025-08-11T20:13:21.946Z","avatar_url":"https://github.com/wjayesh.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# dapr-distributed-calendar\n\nThis is a sample application built using Dapr as a proof-of-concept. I have experimented with the state store, pubsub and output bindings features available with Dapr.\nI have used multiple languages for writing the different parts of this calendar app. This demonstrates the language-agnostic nature of Dapr and the flexibility that it bings to developing\napplications.\n\n## Contents\n\n* [**Motivation**](https://github.com/wjayesh/dapr-distributed-calendar#motivation)\n\n* [**Architecture**](https://github.com/wjayesh/dapr-distributed-calendar#architecture)\n  * [**Controller**](https://github.com/wjayesh/dapr-distributed-calendar#controller-written-in-javascript)\n  * [**Services**](https://github.com/wjayesh/dapr-distributed-calendar#services)\n\n* [**How to run**](https://github.com/wjayesh/dapr-distributed-calendar#how-to-run)\n\n* [**Test using Postman**](https://github.com/wjayesh/dapr-distributed-calendar#test-using-postman)\n\n\n## Motivation\n\nI am really enthusiastic about cool open source projects and I'm a fan of Azure. When I learnt about Dapr early this year, I knew I needed to get my hands dirty playing\naround with what Dapr had to offer.\nI wanted to explore Dapr an experience building a distributed application with it to understand what it brought to the table \nin comparison to conventional applications. \n\nI had built a SpringBoot app (LINK) on MVCS architecture before; it was a monolith application, all written in Java. \nBuilding a roughly similar architecture as a distributed applicaiton would intuitively require some additional work pertaining to service discovery, inter-pod communication\nand network security. Things could get complicated if I needed additional checks, statestores or other controls which I would have to implement on my own.\nThis, in addition to the actual application itself. \n\nI wanted to find out how Dapr simplified this process and what additional work I would have to put in to get a distributed version of the same applciation using Dapr. \n\n## Architecture\n\nI have tried to model this system on the Model View Controller Service (MVCS) architecture, as already mentioned. \n\n\n![Diagram](https://user-images.githubusercontent.com/37150991/100470841-07c3d380-30ff-11eb-855c-711026ad4804.png)\n\n\n### Controller (written in Javascript)\n\n  * The controller supports creation of new events and deletion of existing events. \n    It forwards these requests to the **Go** code using service invocation.\n  \n    *Shown below is the add event flow*. \n  \n    ```js\n    app.post('/newevent', (req, res) =\u003e {\n    const data = req.body.data;\n    const eventId = data.id;\n    console.log(\"New event registration! Event ID: \" + eventId);\n\n\n    console.log(\"Data passed as body to Go\", JSON.stringify(data))\n    fetch(invokeUrl+`/addEvent`, {\n        method: \"POST\",\n        body: JSON.stringify(data),\n        headers: {\n            \"Content-Type\": \"application/json\"\n        }\n    })\n    ```\n    where the invokeURL is defined as:\n    ```js\n    const invokeUrl = `http://localhost:${daprPort}/v1.0/invoke/${eventApp}/method`;\n    ```\n  \n  \n  * On creation of a new event, it publishes a message to a **pubsub** topic which is then picked up by the **Python** subscriber. \n  \n    *Pubishing to the topic*\n  \n    ```js\n    function send_notif(data) {\n      var message = {\n          \"data\": {\n              \"message\": data,\n          }\n      };\n      console.log(\"Message: \", message)\n      request( { uri: publishUrl, method: 'POST', json: JSON.stringify(message) } );\n    }\n    ```\n    where the publish URL is:\n    ```js\n    const publishUrl = `http://localhost:${daprPort}/v1.0/publish/${pubsub_name}/${topic}`;\n    ```\n    \n### Services\n\nThe services handle the requests forwarded by the controller. Each of the tasks listed with the controller is handled by a service written in \na different language. I'll detail the implementation below.\n\n* **Event Service** (written in Go):\n  This service uses the statestore component Redis for storing and deleting events from memory. The code snippet shown below is from \n  `go_events.go` and demonstrates adding an event to the state store. \n\n  ```go\n   var data = make([]map[string]string, 1)\n   data[0] = map[string]string{\n    \"key\":   event.ID,\n    \"value\": event.Name + \" \" + event.Date,\n   }\n   state, _ := json.Marshal(data)\n   log.Printf(string(state))\n\n\n   resp, err := http.Post(stateURL, \"application/json\", bytes.NewBuffer(state))\n  ```\n\n  where the stateURL is defined as:\n\n\n  ```go\n  var stateURL = fmt.Sprintf(`http://localhost:%s/v1.0/state/%s`, daprPort, stateStoreName)\n  ```\n\n* **Messaging Service** (written in Python):\n\n  This service subscribes to the topic that we post messages to, from the controller. It then uses the [SendGrid](https://docs.dapr.io/operations/components/setup-   bindings/supported-bindings/sendgrid/) output binding to \n  send an email about creation of a new event. \n  I have used the Dapr client for Python while writing this service. \n\n  The code below shows how the service registers as a **subscriber** with Dapr for a specific topic.\n  \n\n  ```python\n  @app.route('/dapr/subscribe', methods=['GET'])\n  def subscribe():\n      subscriptions = [{'pubsubname': 'pubsub',\n                        'topic': 'events-topic',\n                        'route': 'getmsg'}]\n      return jsonify(subscriptions)\n  ```\n  \n  \u003e The Dapr runtime calls the `/dapr/subscribe` endpoint to register new apps as subscribers. The other way to do this would be defining a configuration\n  file, linked [here](https://github.com/dapr/docs/blob/3509967baa65ece9fb822e2948e4eb7ed8d34af5/daprdocs/content/en/developing-applications/building-blocks/pubsub/howto-publish-subscribe.md#declarative-subscriptions). \n  \n  The following code receives the message posted to the topic and then calls the `send_email` function.\n  \n  ```py\n  @app.route('/getmsg', methods=['POST'])\n  def subscriber():\n    print(request.json, flush=True)\n    \n    jsonRequest = request.json\n    data = jsonRequest[\"data\"][\"data\"][\"message\"]\n    print(data, flush=True)\n    \n    send_email()\n  ```\n\n  The send_email functions calls the SendGrid binding with the message payload:\n  \n  ```py\n  def send_email():\n    with DaprClient() as d:\n            \n        req_data = {\n            'metadata': {\n                'emailTo': emailTo,\n                'subject': subject\n            },\n            'data': data\n        }\n\n\n        print(req_data, flush=True)\n\n\n        # Create a typed message with content type and body\n        resp = d.invoke_binding(binding_name, 'create', json.dumps(req_data))\n  ```\n  \n  where invoke_binding is a library function from the Dapr client. In the previous cases, we had called the endpoints directly; here we \n  use a function already implemented for us.\n\n\n## How to Run\n\nThe project has three different apps, in Go, Python and Node. We have to build them and then utilise the `dapr run` command to start these apps.\n\nFirst, make sure that your component definitions are present under `$HOME/.dapr/components` if you're on Linux and under `%USERPROFILE%\\.dapr\\components` if you're using Windows. This is because the `dapr run` command makes use of the yaml definitions provided here at runtime. \n \nYou can find the component definitions I've used in this project under `components` of the root directory. The password and api keys have been removed and will need to be provided at runtime. In Kubernetes, you can make use of Secrets for these values. Check out [secret stores](https://docs.dapr.io/developing-applications/building-blocks/secrets/secrets-overview/) component of Dapr!\n\nAfter the components are created with the correct fields, we can build and run the individual apps.\n\n### Go\n\n1) Go inside the `go` directory and build the project Make sure you have `gorilla/mux`  package installed. If not, run the following command:\n\n```\ngo get -u github.com/gorilla/mux\n```\n2) Build the app.\n\n```\ngo build go_events.go\n```\n3) Run Dapr\n\n```\ndapr run --app-id go-events --app-port 6000 --dapr-http-port 3503 ./go-events\n```\n\n### Python\n\n1) Install required dependencies. \n\n```\npip3 install wheel python-dotenv flask_cors flask dapr\n```\n\n2) Set environment variable for Flask.\n\n```bash\n#Linux/Mac OS:\nexport FLASK_RUN_PORT=5000\n\n#Windows:\nset FLASK_RUN_PORT=5000\n```\n\n3) Start Dapr.\n\n```\ndapr run --app-id messages --app-port 5000 --dapr-http-port 3501 flask run\n```\n\n### Node\n\n1) Install dependencies.\n\n```\nnpm install\n```\n\n2) Start Dapr.\n\n```\ndapr run --app-id controller --app-port 3000 --dapr-http-port 3500 node node_controller.js\n```\n\n## Test Using Postman\n\n### Posting a new event to the controller:\n\n  * Postman client used to send request body at \"/newevent\" endpoint.\n    \u003cimg width=\"960\" alt=\"Postman_xZToyg3V5V\" src=\"https://user-images.githubusercontent.com/37150991/103376151-decd9e80-4b01-11eb-899e-526077298036.png\"\u003e\n\n  * The Dapr logs inside the **controller** showing the data that is being passed to the Go app for persisting in storage. \n\n    \u003cimg width=\"723\" alt=\"Code_q47W3rzXWh\" src=\"https://user-images.githubusercontent.com/37150991/103376099-bcd41c00-4b01-11eb-962d-381dada3b8d4.png\"\u003e\n\n  * The Dapr logs inside the **go-events** app showing the data received along with the status response. \n\n    \u003cimg width=\"731\" alt=\"Code_ofFsZ9hUTU\" src=\"https://user-images.githubusercontent.com/37150991/103376139-d5443680-4b01-11eb-98e8-8ae83fc2c5dd.png\"\u003e\n\n\n\n### Invoking the state endpoint to test statestore feature:\n\n  * Posting to the **events** statestore, a body with a key-value pair as shown. \n\n    \u003cimg width=\"960\" alt=\"Postman_vOcZin57HI\" src=\"https://user-images.githubusercontent.com/37150991/103376192-fc9b0380-4b01-11eb-8aae-3dbf2e744672.png\"\u003e\n\n  * Checking the value of key at the events statestore.\n\n    \u003cimg width=\"960\" alt=\"Postman_BLqZjqVt5x\" src=\"https://user-images.githubusercontent.com/37150991/103376177-ed1bba80-4b01-11eb-9be7-012d0d9ed324.png\"\u003e\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwjayesh%2Fdapr-distributed-calendar","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwjayesh%2Fdapr-distributed-calendar","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwjayesh%2Fdapr-distributed-calendar/lists"}