From 937dda4bdd0c667725def457d87a2bcbbd31c4e7 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 --- Dockerfile | 19 +++++ README.MD | 112 ++++++++++++++++++++++++++++++ cmd/main.go | 10 +++ go.mod | 12 ++++ go.sum | 13 ++++ internal/config/config.go | 13 ++++ internal/consumer/notification.go | 62 +++++++++++++++++ internal/domain/notification.go | 18 +++++ internal/notifier/email.go | 16 +++++ internal/notifier/sms.go | 16 +++++ internal/rabbitmq/connection.go | 21 ++++++ 11 files changed, 312 insertions(+) create mode 100644 Dockerfile create mode 100644 README.MD create mode 100644 cmd/main.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/config/config.go create mode 100644 internal/consumer/notification.go create mode 100644 internal/domain/notification.go create mode 100644 internal/notifier/email.go create mode 100644 internal/notifier/sms.go create mode 100644 internal/rabbitmq/connection.go diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b5f9de3 --- /dev/null +++ b/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/README.MD b/README.MD new file mode 100644 index 0000000..1a573f1 --- /dev/null +++ b/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/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..845e0aa --- /dev/null +++ b/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/go.mod b/go.mod new file mode 100644 index 0000000..8c97b6b --- /dev/null +++ b/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/go.sum b/go.sum new file mode 100644 index 0000000..27459d5 --- /dev/null +++ b/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/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..2f86d1c --- /dev/null +++ b/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/internal/consumer/notification.go b/internal/consumer/notification.go new file mode 100644 index 0000000..a32571c --- /dev/null +++ b/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/internal/domain/notification.go b/internal/domain/notification.go new file mode 100644 index 0000000..be5bc44 --- /dev/null +++ b/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/internal/notifier/email.go b/internal/notifier/email.go new file mode 100644 index 0000000..fb4b52d --- /dev/null +++ b/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/internal/notifier/sms.go b/internal/notifier/sms.go new file mode 100644 index 0000000..ab495d8 --- /dev/null +++ b/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/internal/rabbitmq/connection.go b/internal/rabbitmq/connection.go new file mode 100644 index 0000000..2c432e1 --- /dev/null +++ b/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 +}