From a3e8ed3e368908997880188bc66e15f5b9dbccf1 Mon Sep 17 00:00:00 2001 From: A'zamov Samandar Date: Sun, 20 Apr 2025 16:15:04 +0500 Subject: [PATCH] feat: Notification service qo'shildi va RabbitMQ integratsiya qilindi --- Makefile | 2 +- docker-compose.yml | 22 ++++ notification/Dockerfile | 19 +++ notification/README.MD | 112 ++++++++++++++++++ notification/cmd/main.go | 10 ++ notification/go.mod | 12 ++ notification/go.sum | 13 ++ notification/internal/config/config.go | 13 ++ .../internal/consumer/notification.go | 62 ++++++++++ notification/internal/domain/notification.go | 18 +++ notification/internal/notifier/email.go | 16 +++ notification/internal/notifier/sms.go | 16 +++ notification/internal/rabbitmq/connection.go | 21 ++++ test.py | 17 +++ user/docker-compose.yml | 10 +- 15 files changed, 357 insertions(+), 6 deletions(-) create mode 100644 notification/Dockerfile create mode 100644 notification/README.MD create mode 100644 notification/cmd/main.go create mode 100644 notification/go.mod create mode 100644 notification/go.sum create mode 100644 notification/internal/config/config.go create mode 100644 notification/internal/consumer/notification.go create mode 100644 notification/internal/domain/notification.go create mode 100644 notification/internal/notifier/email.go create mode 100644 notification/internal/notifier/sms.go create mode 100644 notification/internal/rabbitmq/connection.go create mode 100644 test.py diff --git a/Makefile b/Makefile index e43734d..d71cf8d 100644 --- a/Makefile +++ b/Makefile @@ -7,4 +7,4 @@ up-payment: down-payment: docker compose --profile payment down down-all: - docker compose --profile payment --profile user down \ No newline at end of file + docker compose --profile payment --profile user --profile notification down \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 02e2e42..e15fdf0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,6 +5,7 @@ networks: volumes: pg_data: null pycache: null + rabbitmq-data: null services: traefik: @@ -94,8 +95,29 @@ services: profiles: - payment + notification: + build: + context: ./notification + dockerfile: Dockerfile + networks: + - lamenu + profiles: + - notification + redis: networks: - lamenu restart: always image: redis + + rabbitmq: + image: rabbitmq:management + container_name: rabbitmq + ports: + - "5672:5672" # RabbitMQ porti + - "15672:15672" # Web konsol porti + environment: + - RABBITMQ_DEFAULT_USER=guest # Foydalanuvchi nomi + - RABBITMQ_DEFAULT_PASS=guest # Parol + volumes: + - rabbitmq-data:/var/lib/rabbitmq # Ma'lumotlarni saqlash diff --git a/notification/Dockerfile b/notification/Dockerfile new file mode 100644 index 0000000..b5f9de3 --- /dev/null +++ b/notification/Dockerfile @@ -0,0 +1,19 @@ +FROM golang:alpine as build + +WORKDIR /app + +COPY go.mod go.sum ./ + +RUN go mod download + +COPY . . + +RUN go build -o notification ./cmd/main.go + +FROM alpine + +WORKDIR /app + +COPY --from=build /app/notification . + +CMD ["./notification"] diff --git a/notification/README.MD b/notification/README.MD new file mode 100644 index 0000000..1a573f1 --- /dev/null +++ b/notification/README.MD @@ -0,0 +1,112 @@ +# Notification Service + +A microservice for handling and delivering notifications through various channels like SMS and email using RabbitMQ as a message broker. + +## Overview + +This notification service is designed as a standalone microservice that consumes notification requests from a RabbitMQ queue and routes them to the appropriate notification provider based on the notification type. Currently, it supports SMS and email notifications. + +## Features + +- Message consumption from RabbitMQ +- Support for multiple notification channels (SMS, email) +- Extensible architecture for adding new notification types +- Asynchronous notification handling + +## Architecture + +The notification service follows a clean architecture approach: + +- **Domain Layer**: Contains core business logic and port interfaces +- **Infrastructure Layer**: Implements the ports with concrete adapters +- **RabbitMQ**: Used as a message broker for consuming notification requests + +## Installation + +### Prerequisites + +- Go 1.x+ +- RabbitMQ server + +### Setup + +1. Clone the repository: +```bash +git clone https://github.com/JscorpTech/notification.git +cd notification +``` + +2. Install dependencies: +```bash +go mod download +``` + +3. Build the application: +```bash +go build -o notification-service +``` + +## Configuration + +Configure your RabbitMQ connection and other settings in the appropriate configuration files. + +## Usage + +### Running the service + +```bash +./notification-service +``` + +This will start the notification consumer that listens for incoming notification requests. + +### Sending a notification + +Notifications should be published to the RabbitMQ exchange with the following JSON format: + +```json +{ + "type": "email", + "message": "Hello, this is a test notification.", + "to": ["user@example.com"] +} +``` + +Available notification types: +- `email`: For email notifications +- `sms`: For SMS notifications + +## Project Structure + +``` +notification/ +├── cmd/ +│ └── main.go # Entry point +├── internal/ +│ ├── domain/ +│ │ └── ports.go # Interfaces +│ ├── notifier/ +│ │ ├── email.go # Email notification implementation +│ │ └── sms.go # SMS notification implementation +│ ├── rabbitmq/ +│ │ └── connection.go # RabbitMQ connection handling +│ └── consumer/ +│ └── consumer.go # Implementation of the notification consumer +└── README.md +``` + +## Contributing + +1. Fork the repository +2. Create your feature branch: `git checkout -b feature/my-new-feature` +3. Commit your changes: `git commit -am 'Add some feature'` +4. Push to the branch: `git push origin feature/my-new-feature` +5. Submit a pull request + +## License + +[Add your license here] + +## Contact + +JscorpTech - [GitHub](https://github.com/JscorpTech) diff --git a/notification/cmd/main.go b/notification/cmd/main.go new file mode 100644 index 0000000..845e0aa --- /dev/null +++ b/notification/cmd/main.go @@ -0,0 +1,10 @@ +package main + +import ( + "github.com/JscorpTech/notification/internal/consumer" +) + +func main() { + notification := consumer.NewNotificationConsumer() + notification.Start() +} diff --git a/notification/go.mod b/notification/go.mod new file mode 100644 index 0000000..8c97b6b --- /dev/null +++ b/notification/go.mod @@ -0,0 +1,12 @@ +module github.com/JscorpTech/notification + +go 1.24.0 + +require ( + github.com/k0kubun/pp/v3 v3.4.1 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/streadway/amqp v1.1.0 // indirect + golang.org/x/sys v0.5.0 // indirect + golang.org/x/text v0.19.0 // indirect +) diff --git a/notification/go.sum b/notification/go.sum new file mode 100644 index 0000000..27459d5 --- /dev/null +++ b/notification/go.sum @@ -0,0 +1,13 @@ +github.com/k0kubun/pp/v3 v3.4.1 h1:1WdFZDRRqe8UsR61N/2RoOZ3ziTEqgTPVqKrHeb779Y= +github.com/k0kubun/pp/v3 v3.4.1/go.mod h1:+SiNiqKnBfw1Nkj82Lh5bIeKQOAkPy6Xw9CAZUZ8npI= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/streadway/amqp v1.1.0 h1:py12iX8XSyI7aN/3dUT8DFIDJazNJsVJdxNVEpnQTZM= +github.com/streadway/amqp v1.1.0/go.mod h1:WYSrTEYHOXHd0nwFeUXAe2G2hRnQT+deZJJf88uS9Bg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= diff --git a/notification/internal/config/config.go b/notification/internal/config/config.go new file mode 100644 index 0000000..2f86d1c --- /dev/null +++ b/notification/internal/config/config.go @@ -0,0 +1,13 @@ +package config + +import ( + "os" +) + +func GetEnv(key, fallback string) string { + val := os.Getenv(key) + if val == "" { + return fallback + } + return val +} diff --git a/notification/internal/consumer/notification.go b/notification/internal/consumer/notification.go new file mode 100644 index 0000000..a32571c --- /dev/null +++ b/notification/internal/consumer/notification.go @@ -0,0 +1,62 @@ +package consumer + +import ( + "encoding/json" + "fmt" + "log" + + "github.com/JscorpTech/notification/internal/domain" + "github.com/JscorpTech/notification/internal/notifier" + "github.com/JscorpTech/notification/internal/rabbitmq" + "github.com/streadway/amqp" +) + +type notificationConsumer struct{} + +func NewNotificationConsumer() domain.NotificationConsumerPort { + return ¬ificationConsumer{} +} + +func (n *notificationConsumer) Start() { + conn, ch, err := rabbitmq.Connect() + if err != nil { + log.Fatal(err) + } + defer conn.Close() + defer ch.Close() + + exchangeName := "notification" + queueName := "notification" + routingKey := "notification" + + ch.ExchangeDeclare(exchangeName, "direct", true, false, false, false, nil) + q, _ := ch.QueueDeclare(queueName, true, false, false, false, nil) + ch.QueueBind(q.Name, routingKey, exchangeName, false, nil) + + msgs, _ := ch.Consume(q.Name, "", true, false, false, false, nil) + + go func() { + for msg := range msgs { + go n.Handler(msg) + } + }() + + fmt.Println("🚀 Server started. Ctrl+C to quit.") + select {} +} + +func (n *notificationConsumer) Handler(msg amqp.Delivery) { + var notification domain.NotificationMsg + err := json.Unmarshal(msg.Body, ¬ification) + if err != nil { + fmt.Print(err.Error()) + } + var ntf domain.NotifierPort + switch notification.Type { + case "sms": + ntf = notifier.NewSmsNotifier() + case "email": + ntf = notifier.NewEmailNotifier() + } + ntf.SendMessage(notification.To, notification.Message) +} diff --git a/notification/internal/domain/notification.go b/notification/internal/domain/notification.go new file mode 100644 index 0000000..be5bc44 --- /dev/null +++ b/notification/internal/domain/notification.go @@ -0,0 +1,18 @@ +package domain + +import "github.com/streadway/amqp" + +type NotificationConsumerPort interface { + Start() + Handler(amqp.Delivery) +} + +type NotifierPort interface { + SendMessage([]string, string) +} + +type NotificationMsg struct { + Type string `json:"type"` + Message string `json:"message"` + To []string `json:"to"` +} diff --git a/notification/internal/notifier/email.go b/notification/internal/notifier/email.go new file mode 100644 index 0000000..fb4b52d --- /dev/null +++ b/notification/internal/notifier/email.go @@ -0,0 +1,16 @@ +package notifier + +import ( + "github.com/JscorpTech/notification/internal/domain" + "github.com/k0kubun/pp/v3" +) + +type emailNotifier struct{} + +func NewEmailNotifier() domain.NotifierPort { + return &emailNotifier{} +} + +func (n *emailNotifier) SendMessage(to []string, body string) { + pp.Print(to, body) +} diff --git a/notification/internal/notifier/sms.go b/notification/internal/notifier/sms.go new file mode 100644 index 0000000..ab495d8 --- /dev/null +++ b/notification/internal/notifier/sms.go @@ -0,0 +1,16 @@ +package notifier + +import ( + "github.com/JscorpTech/notification/internal/domain" + "github.com/k0kubun/pp/v3" +) + +type smsNotifier struct{} + +func NewSmsNotifier() domain.NotifierPort { + return &smsNotifier{} +} + +func (n *smsNotifier) SendMessage(to []string, body string) { + pp.Print(to, body) +} diff --git a/notification/internal/rabbitmq/connection.go b/notification/internal/rabbitmq/connection.go new file mode 100644 index 0000000..2c432e1 --- /dev/null +++ b/notification/internal/rabbitmq/connection.go @@ -0,0 +1,21 @@ +package rabbitmq + +import ( + "log" + + "github.com/streadway/amqp" +) + +func Connect() (*amqp.Connection, *amqp.Channel, error) { + conn, err := amqp.Dial("amqp://guest:guest@127.0.0.1:5672/") + if err != nil { + return nil, nil, err + } + ch, err := conn.Channel() + if err != nil { + return nil, nil, err + } + log.Println("🐇 Connected to RabbitMQ") + + return conn, ch, nil +} diff --git a/test.py b/test.py new file mode 100644 index 0000000..bb53ca4 --- /dev/null +++ b/test.py @@ -0,0 +1,17 @@ +from kombu import Connection, Exchange, Producer + +# RabbitMQ ulanishi +rabbit_url = 'amqp://guest:guest@127.0.0.1:5672/' +connection = Connection(rabbit_url) +channel = connection.channel() + +exchange = Exchange('notification', type='direct') + +# Producer yaratish +producer = Producer(channel, exchange=exchange, routing_key="notification") + +# Xabar yuborish +message = {'type': 'email', 'message': 'Hello, Workers!', "to": ["+998888112309", "+998943990509"]} +producer.publish(message) + +print("Message sent to all workers!") diff --git a/user/docker-compose.yml b/user/docker-compose.yml index cc717e8..b80c25b 100644 --- a/user/docker-compose.yml +++ b/user/docker-compose.yml @@ -1,5 +1,5 @@ networks: - auth: + user: driver: bridge volumes: @@ -10,7 +10,7 @@ volumes: services: nginx: networks: - - auth + - user ports: - ${PORT:-8001}:80 volumes: @@ -23,7 +23,7 @@ services: - web web: networks: - - auth + - user build: context: . dockerfile: ./docker/Dockerfile.web @@ -39,7 +39,7 @@ services: - redis db: networks: - - auth + - user image: postgres:16 restart: always environment: @@ -50,6 +50,6 @@ services: - pg_data:/var/lib/postgresql/data redis: networks: - - auth + - user restart: always image: redis \ No newline at end of file