{"id":26773154,"url":"https://github.com/nichitaa/notifyx","last_synced_at":"2026-04-18T01:07:42.942Z","repository":{"id":60958050,"uuid":"536746304","full_name":"nichitaa/notifyx","owner":"nichitaa","description":"Notification systems with micro services in Elixir. Building distributed applications course","archived":false,"fork":false,"pushed_at":"2022-11-14T09:20:12.000Z","size":2197,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"dev","last_synced_at":"2025-03-29T01:38:27.725Z","etag":null,"topics":["docker-compose","ecto","elixir-phoenix","grafana-dashboard","pm2","postgres","prometheus","scalable","two-phase-commit","ws-api"],"latest_commit_sha":null,"homepage":"","language":"Elixir","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/nichitaa.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":"2022-09-14T20:19:17.000Z","updated_at":"2025-01-17T22:21:00.000Z","dependencies_parsed_at":"2022-10-07T12:59:00.651Z","dependency_job_id":null,"html_url":"https://github.com/nichitaa/notifyx","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/nichitaa/notifyx","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nichitaa%2Fnotifyx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nichitaa%2Fnotifyx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nichitaa%2Fnotifyx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nichitaa%2Fnotifyx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nichitaa","download_url":"https://codeload.github.com/nichitaa/notifyx/tar.gz/refs/heads/dev","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nichitaa%2Fnotifyx/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31952231,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-18T00:39:45.007Z","status":"ssl_error","status_checked_at":"2026-04-18T00:39:20.671Z","response_time":62,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["docker-compose","ecto","elixir-phoenix","grafana-dashboard","pm2","postgres","prometheus","scalable","two-phase-commit","ws-api"],"created_at":"2025-03-29T01:36:19.722Z","updated_at":"2026-04-18T01:07:42.917Z","avatar_url":"https://github.com/nichitaa.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"### Notifyx (Notification system)\n\n\u003e Pasecinic Nichita\n\u003e\n\u003e Distributed systems\n\n#### _Tech Stack_\n\n* [`Elixir`](https://hexdocs.pm/elixir/Kernel.html)\n* [`NodeJS`](https://nodejs.org/en/) - [`expressjs`](https://expressjs.com/)\n* [`Phoenix`](https://hexdocs.pm/phoenix/overview.html) with [`Channels`](https://hexdocs.pm/phoenix/channels.html) - WS\n  API for Gateway and REST API for dedicated\n  services\n* [`Nebulex`](https://hexdocs.pm/nebulex/Nebulex.html) (for caching / service levels and gateway level)\n* [`Postgres`](https://www.postgresql.org/) (will use [`Ecto`](https://hexdocs.pm/ecto/Ecto.html) as db wrapper\n  with [`postrex`](https://github.com/elixir-ecto/postgrex) adapter)\n* [`React/TS`](https://reactjs.org/) (client test application)\n* [`Prometheus`](https://prometheus.io/docs/introduction/overview/) (for metrics scraping)\n* [`Grafana`](https://grafana.com/docs/) (for visual metrics monitoring)\n* [`Docker`](https://docs.docker.com/compose/) (`docker-compose` for CI/deployment)\n\n#### _[Naming is hard](https://quotesondesign.com/phil-karlton/)_\n\nEach service / component will be in a dedicated folder ⚙\n\n* [`acai`](./acai) - Gateway with Phoenix Channels (`port: 4000`)\n* [`durian`](./durian) - Auth Service (`port: 5000`)\n* [`kiwi`](./kiwi) - Persistent Service (`port: 6000`)\n* [`counter_2pc`](./counter_2pc) - Users notifications counter (with 2 phase commit support) (`port: 2000`)\n* [`client`](./client) - Client test application (`port: 3333`)\n* [`guava`](./guava) - Mailing Service (`port: 7000`)\n* [`julik`](./julik) - Service Discovery (`port: 8000`)\n* [`nodex`](./nodex) - Service for generating random stuff (avatars FN) (`port:9000`)\n* [`monitoring`](./monitoring) - Monitoring tools configuration (grafana - `port: 3000`,\n  prometheus: `port: 9090`) - [Dashboards screenshots](./monitoring/README.md)\n\n### Dev Notes 👀\n\n![general-architecture-diagram](./docs/images/general-arhitecture.png)\n\n#### Docker setup 🐳\n\n```shell\ndocker compose up --build --force-recreate\n```\n\n```shell\n# For cleaning up previous Docker images/containers/volumes (run in PowerShell)\n# Don't need to run them on first setup\ndocker rmi -f $(docker images -aq)\ndocker rm -f $(docker ps -a -q)\ndocker volume rm $(docker volume ls -q)\n```\n\n#### Manual setup ⚙\n\n[Start Grafana \u0026 Prometheus Stack as separate Docker containers](./monitoring/README.md)\n\n```shell\ncd monitoring\\local\ndocker compose up --build\n```\n\n```shell\n# 1. Start Gateway (acai)\ncd acai \u0026\u0026 set PORT=4000\u0026\u0026 iex --no-pry --sname gateway_node1 -S mix phx.server\n\n# 2. Start Service Discovery (julik)\ncd julik \u0026\u0026 set PORT=8000\u0026\u0026 iex --no-pry --sname discovery_node1 -S mix phx.server\n\n# 3. Start Auth Service (durian)\ncd durian \u0026\u0026 set PORT=5000\u0026\u0026 iex --no-pry --sname auth_node1 -S mix phx.server\ncd durian \u0026\u0026 set PORT=5001\u0026\u0026 iex --no-pry --sname auth_node2 -S mix phx.server\n\n# 4. Start Persist Service (kiwi)\ncd kiwi \u0026\u0026 set PORT=6000\u0026\u0026 iex --no-pry --sname persist_node1 -S mix phx.server\n\n# 5. Start Mail Service Cluster (guava)\ncd guava \u0026\u0026 set ENABLE_REST_API=1\u0026 set PORT=7000\u0026\u0026 iex --no-pry --sname mail_node1 -S mix phx.server\ncd guava \u0026\u0026 set PORT=7001\u0026\u0026 iex --no-pry --sname mail_node2 -S mix phx.server\ncd guava \u0026\u0026 set PORT=7002\u0026\u0026 iex --no-pry --sname mail_node3 -S mix phx.server\ncd guava \u0026\u0026 set PORT=7003\u0026\u0026 iex --no-pry --sname mail_node4 -S mix phx.server\n\n# 6. Start Generator Service Cluster (nodex)\ncd nodex \u0026\u0026 npm run dev:pm2\n# to stop: npm run del:pm2\n\n# 7. Start Counter 2 Phase commit service (counter_2pc)\ncd counter_2pc \u0026\u0026 set PORT=2000\u0026 iex --no-pry --sname counter_2pc -S mix phx.server\n\n# 8. Start Client application (client)\ncd client \u0026\u0026 npm run dev\n```\n\n#### _What should be implemented (technically) ?_\n\nService Features:\n\n* SQL databases (postgres) - (Auth \u0026 Persist service)\n* Status endpoint `/dashboard` - Generated by Phoenix Framework\n* Task timeouts configurable per individual task - e.g.: inside `config.exs` - `send_email_timeout: 1000`\n* Service Discovery - [`julik`](./julik)\n* RPC - Mailing service Nodes communicates via [`:erpc`](https://www.erlang.org/doc/man/erpc.html)\n* Concurrent task limit - `DynamicSupervisor` for mail workers has a `max_children`\n  configured \u003csup\u003e[link](./guava/config/config.exs)\u003c/sup\u003e\n* Grafana / Prometheus metrics collection \u0026 monitoring\n* 2 phase commit for create notification action (`kiwi` \u0026 `counter_2pc`)\n    * `POST` - `/api/notifications` with `is_2pc_locked: true` (prepare)\n    * `POST` - `/api/notifications/commit_2pc` with `request_id` from prepare step (commit)\n    * `DELETE` - `/api/notifications/rollback_2pc` with `request_id` from prepare step (rollback)\n\nGateway Features:\n\n* Load Balancing - Round Robin\n* Outbound WS API\n* Circuit breaker \u003csup\u003e[link](./acai/lib/acai/circuit_breaker.ex)\u003c/sup\u003e\n* Grafana / Prometheus metrics collection \u0026 monitoring\n* 2 phase commit integration for services that supports it\n    * 1 phase - prepare data request -\u003e success/error\n    * 2 phase - commit/rollback request -\u003e ack/nack\n\nThe Cache:\n\n* Implemented in Auth Service\n* Implemented in Persist Service\n* Replicated cache (across all Auth service nodes - `durian` nodes)\n\nOther:\n\n* Real-time events/notifications via WS API and Phoenix Channels\n\n#### _Some 2 Phase commit implementation notes_\n\nServices should implement several routes for it:\n\n* `POST` - `/api/prepare_2pc`\n* `POST` - `/api/commit_2pc`\n* `DELETE` - `/api/rollback_2pc`\n\nAfter successful `prepare` request it might return an identifier for the created transaction (mutation) or ack/nack:\n\n* `kiwi` - will return `request_id` (later used to rollback/commit transaction)\n* `counter_2pc` - just ack (`user_id` from request is enough to rollback/commit transaction)\n\nActually the terminology of `commit/rollback transaction` should be better called `save/discard data actions`. As there\nis no\nreal transaction reference that could be later used to commit/rollback it, the data is still persisted somewhere and\nthere\nshould exist a clean-up/save handlers for each of the atomic change\n\n#### The [`Manager2PC`](./acai/lib/services/manager_2pc.ex)\n\nIs the generic implementation for handling first and second phase from 2 phase-commit requests.\nThe prepare phase is domain specific, meaning that it should be clearly defined all prepare tasks handlers like:\n\n```elixir\nprepare_tasks = [\n  Task.async(fn -\u003e\n    Services.Persist.init_2pc(socket, notification)\n  end),\n  Task.async(fn -\u003e\n    Services.Counter.init_2pc(socket)\n  end)\n]\n```\n\n`init_2pc` function should return a tuple of:\n\n```elixir\n{:ok, commit_fn, rollback_fn}\n{:error, data}\n```\n\n`commit_fn` and `rollback_fn` are as well domain specific so those should be handled by the dedicated services\nseparately.\n\nThe second phase is executing either `commit_fn` or `rollback_fn` handlers, based on response from prepare (first)\nphase.\nA big disadvantage of 2 phase commit approach is that there is no clear definition of what should happen when a task\nfrom second phase\nfails (either to commit or rollback).\n\nNote that tasks from both phases are done asynchronously with `Task.async/1` and awaited with `Task.await_many/2`.\n\n#### _What should be implemented (business-wise) ?_\n\n* User could send, receive, ack notifications/messages in real-time\n* Multiple \u0026 dynamic notifications topics created by users (all other operations will validate topic creators)\n* Service for persist and keep track of the broadcasted notifications, topics, subscriptions\n* Service for sending notifications via email\n* Service for generating PNG avatars\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnichitaa%2Fnotifyx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnichitaa%2Fnotifyx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnichitaa%2Fnotifyx/lists"}