0% found this document useful (0 votes)
7 views

Capstone Project

Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
7 views

Capstone Project

Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 32

Overview: Payment Gateway/E-Wallet with Bank

Reconciliation
Services Overview

1. User Service
Description: Manages user-related operations such as registration, authentication,
and profile management.
Functionalities:
User Registration: Allows new users to create an account.
User Login: Authenticates users using JWT (JSON Web Tokens).
Profile Management: Enables users to update their profile information.
Interactions:
Interacts with the Account Service to create a user wallet upon registration.
Validates JWT tokens for other services to ensure secure access.
2. Account Service
Description: Manages wallet accounts, including creating, updating, and retrieving
account details.
Functionalities:
Create Wallet: Generates a new wallet account for users.
Update Wallet: Allows users to update wallet details.
Retrieve Wallet: Fetches wallet information for users.
Interactions:
Interacts with the User Service to link wallets to user profiles.
Communicates with the Transaction Service to handle financial operations.
3. Transaction Service
Description: Handles financial transactions, including deposits, withdrawals,
transfers, and payments.
Functionalities:
Deposit Funds: Adds money to a user’s wallet.
Withdraw Funds: Allows users to withdraw money from their wallet.
Transfer Funds: Enables transferring money between wallets.
Process Payments: Handles payments to third-party vendors.
Interactions:
Works with the Account Service to update wallet balances.
Interacts with the Payment Gateway Service to process external payments.
Uses the Notification Service to send transaction alerts.
4. Payment Gateway Service
Description: Integrates with third-party payment gateways for processing payments.
Functionalities:
Process Payment: Interfaces with external payment gateways like Stripe to
handle transactions.
Verify Payment: Confirms payment status and updates the transaction record.
Interactions:
Communicates with the Transaction Service to process and verify payments.
Sends status updates to the Bank Reconciliation Service for record-keeping.
5. Bank Reconciliation Service
Description: Reconciles transactions with bank records to ensure accuracy and
consistency.
Functionalities:
Reconcile Transactions: Matches internal transaction records with bank
statements.
Generate Reports: Creates reconciliation reports for audit purposes.
Interactions:
Interfaces with the Payment Gateway Service to verify transaction records.
Works with the Transaction Service to update and correct transaction
discrepancies.
6. Notification Service
Description: Sends real-time notifications for account activities and transactions.
Functionalities:
Send Email Notifications: Sends email alerts for various account activities.
Send SMS Notifications: Sends SMS alerts for critical transactions.
Real-time Alerts: Uses WebSockets to provide instant notifications.
Interactions:
Communicates with the Transaction Service to send transaction alerts.
Interacts with the User Service to manage notification preferences.
7. Caching Service
Description: Implements caching for frequently accessed data to improve
performance.
Functionalities:
Cache User Data: Stores user profile data for quick retrieval.
Cache Transactions: Caches recent transactions to reduce load on the
Transaction Service.
Interactions:
Works with all services to cache and retrieve data, improving overall
performance.

Interactions and Data Flow


User Service and Account Service: When a new user registers, the User Service
communicates with the Account Service to create a wallet for the user.
Transaction Service and Payment Gateway Service: For processing payments, the
Transaction Service interfaces with the Payment Gateway Service to handle the
transaction and verify its status.
Bank Reconciliation Service and Payment Gateway Service: The Bank Reconciliation
Service ensures that all processed payments match the records provided by the Payment
Gateway Service.
Transaction Service and Notification Service: Every transaction processed by the
Transaction Service triggers the Notification Service to send real-time alerts to users.
Caching Service: All services leverage the Caching Service to store and retrieve
frequently accessed data, reducing the load on databases and improving response times.

Deployment and Management


Containerization: Each service is containerized using Docker for consistent deployment
across environments.
Orchestration: Kubernetes is used for deploying and managing the services, ensuring
scalability and high availability.
Ingress Controller: Traefik is configured as the ingress controller to handle external traffic
and load balancing.
GitOps: ArgoCD is utilized for continuous delivery and GitOps practices, automating the
deployment process and ensuring that the deployment state matches the desired state
defined in Git repositories.

Integration Test for Common Workflow


Directory Structure for Tests

payment-gateway/
├── user-service/
│ └── main_test.go
├── account-service/
│ └── main_test.go
├── transaction-service/
│ └── main_test.go
├── payment-gateway-service/
│ └── main_test.go
├── bank-reconciliation-service/
│ └── main_test.go
├── notification-service/
│ └── main_test.go
├── caching-service/
│ └── main_test.go
└── tests/
└── integration_test.go

Integration Test ( tests/integration_test.go )

package tests

import (
"bytes"
"encoding/json"
"net/http"
"testing"
"time"
)

const (
userServiceURL = "http://localhost:8080"
accountServiceURL = "http://localhost:8081"
transactionServiceURL = "http://localhost:8082"
paymentGatewayURL = "http://localhost:8083"
notificationServiceURL = "http://localhost:8085"
reconciliationServiceURL = "http://localhost:8084"
)

func TestCommonWorkflow(t *testing.T) {


// Step 1: Register User
user := map[string]string{
"username": "testuser",
"password": "password",
"email": "[email protected]",
}
if err := post(userServiceURL+"/register", user); err != nil {
t.Fatalf("Failed to register user: %v", err)
}

// Step 2: Login User


token, err := loginUser(userServiceURL, user)
if err != nil {
t.Fatalf("Failed to login user: %v", err)
}

// Step 3: Create Account


account := map[string]interface{}{
"user_id": 1,
"balance": 1000.0,
}
if err := post(accountServiceURL+"/accounts", account); err != nil {
t.Fatalf("Failed to create account: %v", err)
}

// Step 4: Make a Transaction


transaction := map[string]interface{}{
"account_id": 1,
"amount": 100.0,
"type": "deposit",
}
if err := post(transactionServiceURL+"/transactions", transaction); err
!= nil {
t.Fatalf("Failed to create transaction: %v", err)
}

// Step 5: Process Payment


payment := map[string]interface{}{
"amount": 100.0,
"currency": "USD",
"method": "credit_card",
}
if err := post(paymentGatewayURL+"/payments", payment); err != nil {
t.Fatalf("Failed to process payment: %v", err)
}

// Step 6: Send Notification


notification := map[string]interface{}{
"user_id": 1,
"message": "Your transaction has been processed.",
}
if err := post(notificationServiceURL+"/notifications", notification);
err != nil {
t.Fatalf("Failed to send notification: %v", err)
}

// Step 7: Reconcile with Bank


reconciliation := map[string]interface{}{
"transactions": []map[string]interface{}{
{
"account_id": 1,
"amount": 100.0,
"type": "deposit",
},
},
}
if err := post(reconciliationServiceURL+"/reconcile", reconciliation);
err != nil {
t.Fatalf("Failed to reconcile transactions: %v", err)
}
}

func post(url string, data interface{}) error {


jsonData, err := json.Marshal(data)
if err != nil {
return err
}

resp, err := http.Post(url, "application/json",


bytes.NewBuffer(jsonData))
if err != nil {
return err
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK && resp.StatusCode !=


http.StatusCreated {
return fmt.Errorf("expected status 200 OK or 201 Created, got %v",
resp.StatusCode)
}

return nil
}

func loginUser(url string, user map[string]string) (string, error) {


loginData, err := json.Marshal(user)
if err != nil {
return "", err
}

resp, err := http.Post(url+"/login", "application/json",


bytes.NewBuffer(loginData))
if err != nil {
return "", err
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("expected status 200 OK, got %v",
resp.StatusCode)
}

var result map[string]string


json.NewDecoder(resp.Body).Decode(&result)

return result["token"], nil


}

Explanation
1. Register User: Registers a new user by sending a POST request to the User Service.
2. Login User: Logs in the user and retrieves a JWT token.
3. Create Account: Creates a new account for the user.
4. Make a Transaction: Creates a transaction (e.g., deposit) for the account.
5. Process Payment: Simulates a payment using the Payment Gateway Service.
6. Send Notification: Sends a notification about the transaction.
7. Reconcile with Bank: Simulates bank reconciliation for the transaction.

Running the Test


To run the test, ensure all the services are running locally (or in your Kubernetes cluster).
Execute the test using the go test command.

go test ./tests -v

Enhancements
Setup and Teardown: Implement setup and teardown functions to create a test
environment and clean up after tests.
Environment Variables: Use environment variables to configure service URLs and other
settings.
Error Handling: Improve error handling and logging for better test diagnostics.
Test Data: Use fixtures or factories to generate test data.

High-Level Architecture
1. User Service
2. Account Service
3. Transaction Service
4. Payment Gateway Service
5. Bank Reconciliation Service
6. Notification Service
7. Caching Service

Tech Stack
Language: Go 1.2x
Database: PostgreSQL
Cache: Redis
API Gateway and Ingress: Traefik
Containerization: Docker
Orchestration: Kubernetes
Continuous Deployment: ArgoCD

Initial Setup
1. Repository Structure
2. Common Libraries and Packages
3. Docker Configuration
4. Kubernetes Manifests

1. Repository Structure

payment-gateway/
├── user-service/
├── account-service/
├── transaction-service/
├── payment-gateway-service/
├── bank-reconciliation-service/
├── notification-service/
├── caching-service/
├── common/
│ ├── middleware/
│ ├── models/
│ ├── utils/
│ └── config/
└── deploy/
├── docker/
├── kubernetes/
└── scripts/

2. Common Libraries and Packages


Middleware: JWT authentication, logging, etc.
Models: Shared data models across services.
Utils: Common utility functions.
Config: Configuration management.

Example: Config Package ( common/config/config.go )

package config

import (
"github.com/spf13/viper"
)

type Config struct {


DBHost string
DBPort string
DBUser string
DBPassword string
DBName string
RedisHost string
RedisPort string
JwtSecret string
}

var Cfg *Config


func LoadConfig() {
viper.AddConfigPath(".")
viper.SetConfigName("config")
viper.SetConfigType("yaml")

if err := viper.ReadInConfig(); err != nil {


panic(err)
}

Cfg = &Config{
DBHost: viper.GetString("db.host"),
DBPort: viper.GetString("db.port"),
DBUser: viper.GetString("db.user"),
DBPassword: viper.GetString("db.password"),
DBName: viper.GetString("db.name"),
RedisHost: viper.GetString("redis.host"),
RedisPort: viper.GetString("redis.port"),
JwtSecret: viper.GetString("jwt.secret"),
}
}

3. Docker Configuration
Dockerfile Example ( user-service/Dockerfile )

FROM golang:1.2x-alpine

WORKDIR /app

COPY go.mod ./
COPY go.sum ./
RUN go mod download

COPY . .

RUN go build -o /user-service

EXPOSE 8080

CMD ["/user-service"]

4. Kubernetes Manifests
Deployment Example ( deploy/kubernetes/user-service-
deployment.yaml )

apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
labels:
app: user-service
spec:
replicas: 3
selector:
matchLabels:
app: user-service
template:
metadata:
labels:
app: user-service
spec:
containers:
- name: user-service
image: user-service:latest
ports:
- containerPort: 8080
env:
- name: DB_HOST
valueFrom:
configMapKeyRef:
name: db-config
key: db-host
- name: DB_USER
valueFrom:
secretKeyRef:
name: db-secret
key: db-user
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: db-password
- name: JWT_SECRET
valueFrom:
secretKeyRef:
name: jwt-secret
key: secret
---
apiVersion: v1
kind: Service
metadata:
name: user-service
spec:
selector:
app: user-service
ports:
- protocol: TCP
port: 80
targetPort: 8080

Service Implementations
1. User Service
Functionality: Handles user registration, authentication, and profile management.

package main

import (
"encoding/json"
"log"
"net/http"
"github.com/gorilla/mux"
"github.com/dgrijalva/jwt-go"
"time"
"common/config"
"common/models"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)

var db *gorm.DB
var jwtKey = []byte(config.Cfg.JwtSecret)

type Credentials struct {


Username string `json:"username"`
Password string `json:"password"`
}

type Claims struct {


Username string `json:"username"`
jwt.StandardClaims
}

func main() {
config.LoadConfig()
dsn := "host=" + config.Cfg.DBHost + " user=" + config.Cfg.DBUser + "
password=" + config.Cfg.DBPassword + " dbname=" + config.Cfg.DBName + "
port=" + config.Cfg.DBPort + " sslmode=disable"
var err error
db, err = gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatal("Failed to connect to database:", err)
}
db.AutoMigrate(&models.User{})

router := mux.NewRouter()
router.HandleFunc("/register", Register).Methods("POST")
router.HandleFunc("/login", Login).Methods("POST")
router.HandleFunc("/profile", Profile).Methods("GET")

log.Fatal(http.ListenAndServe(":8080", router))
}

func Register(w http.ResponseWriter, r *http.Request) {


var user models.User
json.NewDecoder(r.Body).Decode(&user)
db.Create(&user)
w.WriteHeader(http.StatusCreated)
}

func Login(w http.ResponseWriter, r *http.Request) {


var creds Credentials
json.NewDecoder(r.Body).Decode(&creds)
var user models.User
db.Where("username = ?", creds.Username).First(&user)
if user.Password != creds.Password {
w.WriteHeader(http.StatusUnauthorized)
return
}

expirationTime := time.Now().Add(5 * time.Minute)


claims := &Claims{
Username: creds.Username,
StandardClaims: jwt.StandardClaims{
ExpiresAt: expirationTime.Unix(),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, err := token.SignedString(jwtKey)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}

http.SetCookie(w, &http.Cookie{
Name: "token",
Value: tokenString,
Expires: expirationTime,
})
}

func Profile(w http.ResponseWriter, r *http.Request) {


cookie, err := r.Cookie("token")
if err != nil {
if err == http.ErrNoCookie {
w.WriteHeader(http.StatusUnauthorized)
return
}
w.WriteHeader(http.StatusBadRequest)
return
}

tokenStr := cookie.Value
claims := &Claims{}
token, err := jwt.ParseWithClaims(tokenStr, claims, func(token
*jwt.Token) (interface{}, error) {
return jwtKey, nil
})

if err != nil {
if err == jwt.ErrSignatureInvalid {
w.WriteHeader(http.StatusUnauthorized)
return
}
w.WriteHeader(http.StatusBadRequest)
return
}

if !token.Valid {
w.WriteHeader(http.StatusUnauthorized)
return
}
var user models.User
db.Where("username = ?", claims.Username).First(&user)
json.NewEncoder(w).Encode(user)
}

2. Account Service
Functionality: CRUD operations for wallet accounts.

package main

import (
"encoding/json"
"log"
"net/http"
"github.com/gorilla/mux"
"common/config"
"common/models"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)

var db *gorm.DB

func main() {
config.LoadConfig()
dsn := "host=" + config.Cfg.DBHost + " user=" + config.Cfg.DBUser + "
password=" + config.Cfg.DBPassword + " dbname=" + config.Cfg.DBName + "
port=" + config.Cfg.DBPort + " sslmode=disable"
var err error
db, err = gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatal("Failed to connect to database:", err)
}
db.AutoMigrate(&models.Account{})

router := mux.NewRouter()
router.HandleFunc("/accounts", CreateAccount).Methods("POST")
router.HandleFunc("/accounts/{id}", GetAccount).Methods("GET")
router.HandleFunc("/accounts/{id}", UpdateAccount).Methods("PUT")
router.HandleFunc("/accounts/{id

}", DeleteAccount).Methods("DELETE")
log.Fatal(http.ListenAndServe(":8081", router))
}

func CreateAccount(w http.ResponseWriter, r *http.Request) {


var account models.Account
json.NewDecoder(r.Body).Decode(&account)
db.Create(&account)
w.WriteHeader(http.StatusCreated)
}

func GetAccount(w http.ResponseWriter, r *http.Request) {


vars := mux.Vars(r)
id := vars["id"]
var account models.Account
db.First(&account, id)
json.NewEncoder(w).Encode(account)
}

func UpdateAccount(w http.ResponseWriter, r *http.Request) {


vars := mux.Vars(r)
id := vars["id"]
var account models.Account
db.First(&account, id)
json.NewDecoder(r.Body).Decode(&account)
db.Save(&account)
w.WriteHeader(http.StatusOK)
}

func DeleteAccount(w http.ResponseWriter, r *http.Request) {


vars := mux.Vars(r)
id := vars["id"]
var account models.Account
db.Delete(&account, id)
w.WriteHeader(http.StatusNoContent)
}

Next Steps
1. Implement remaining services: Repeat the pattern used in User Service and Account
Service to implement Transaction Service, Payment Gateway Service, Bank Reconciliation
Service, Notification Service, and Caching Service.
2. Database Schema: Define and migrate database schema for each service.
3. Inter-Service Communication: Set up REST/gRPC communication between services.
4. Security: Enhance security with TLS, API Gateway, and RBAC.
5. Deployment: Finalize Docker and Kubernetes configurations for all services.
6. Monitoring and Logging: Integrate Prometheus, Grafana, and ELK stack for monitoring
and logging.
7. Testing: Write unit tests and integration tests for all services.

1. Transaction Service
2. Payment Gateway Service
3. Bank Reconciliation Service
4. Notification Service
5. Caching Service

3. Transaction Service
Functionality: Processes transactions, including deposits, withdrawals, transfers, and
payments.

package main

import (
"encoding/json"
"log"
"net/http"
"github.com/gorilla/mux"
"common/config"
"common/models"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)

var db *gorm.DB

func main() {
config.LoadConfig()
dsn := "host=" + config.Cfg.DBHost + " user=" + config.Cfg.DBUser + "
password=" + config.Cfg.DBPassword + " dbname=" + config.Cfg.DBName + "
port=" + config.Cfg.DBPort + " sslmode=disable"
var err error
db, err = gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatal("Failed to connect to database:", err)
}
db.AutoMigrate(&models.Transaction{})

router := mux.NewRouter()
router.HandleFunc("/transactions", CreateTransaction).Methods("POST")
router.HandleFunc("/transactions/{id}", GetTransaction).Methods("GET")

log.Fatal(http.ListenAndServe(":8082", router))
}

func CreateTransaction(w http.ResponseWriter, r *http.Request) {


var transaction models.Transaction
json.NewDecoder(r.Body).Decode(&transaction)

// Simulate transaction processing logic here


if err := processTransaction(&transaction); err != nil {
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
return
}

db.Create(&transaction)
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(transaction)
}

func GetTransaction(w http.ResponseWriter, r *http.Request) {


vars := mux.Vars(r)
id := vars["id"]
var transaction models.Transaction
db.First(&transaction, id)
if transaction.ID == 0 {
w.WriteHeader(http.StatusNotFound)
return
}
json.NewEncoder(w).Encode(transaction)
}

func processTransaction(transaction *models.Transaction) error {


// Placeholder for real transaction processing logic
return nil
}

4. Payment Gateway Service


Functionality: Integrates with third-party payment gateways for payment processing.
package main

import (
"encoding/json"
"log"
"net/http"
"github.com/gorilla/mux"
"common/config"
"common/models"
)

func main() {
config.LoadConfig()

router := mux.NewRouter()
router.HandleFunc("/payments", ProcessPayment).Methods("POST")

log.Fatal(http.ListenAndServe(":8083", router))
}

func ProcessPayment(w http.ResponseWriter, r *http.Request) {


var paymentRequest models.PaymentRequest
json.NewDecoder(r.Body).Decode(&paymentRequest)

// Simulate payment gateway integration here


paymentResponse, err := callPaymentGateway(paymentRequest)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
return
}

w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(paymentResponse)
}

func callPaymentGateway(request models.PaymentRequest)


(models.PaymentResponse, error) {
// Placeholder for actual payment gateway API call
return models.PaymentResponse{
Status: "success",
TransactionID: "12345",
}, nil
}
5. Bank Reconciliation Service
Functionality: Reconciles transactions with bank records.

package main

import (
"encoding/json"
"log"
"net/http"
"github.com/gorilla/mux"
"common/config"
"common/models"
)

func main() {
config.LoadConfig()

router := mux.NewRouter()
router.HandleFunc("/reconcile", ReconcileTransactions).Methods("POST")

log.Fatal(http.ListenAndServe(":8084", router))
}

func ReconcileTransactions(w http.ResponseWriter, r *http.Request) {


var reconciliationRequest models.ReconciliationRequest
json.NewDecoder(r.Body).Decode(&reconciliationRequest)

// Simulate bank reconciliation process here


reconciliationResult, err := reconcileWithBank(reconciliationRequest)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
return
}

w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(reconciliationResult)
}

func reconcileWithBank(request models.ReconciliationRequest)


(models.ReconciliationResult, error) {
// Placeholder for actual bank reconciliation logic
return models.ReconciliationResult{
Status: "reconciled",
Discrepancies: []string{},
}, nil
}

6. Notification Service
Functionality: Sends notifications for account activities.

package main

import (
"encoding/json"
"log"
"net/http"
"github.com/gorilla/mux"
"common/config"
"common/models"
)

func main() {
config.LoadConfig()

router := mux.NewRouter()
router.HandleFunc("/notifications", SendNotification).Methods("POST")

log.Fatal(http.ListenAndServe(":8085", router))
}

func SendNotification(w http.ResponseWriter, r *http.Request) {


var notification models.Notification
json.NewDecoder(r.Body).Decode(&notification)

// Simulate sending notification here


err := sendNotification(notification)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
return
}

w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"status": "sent"})
}

func sendNotification(notification models.Notification) error {


// Placeholder for actual notification sending logic
return nil
}

7. Caching Service
Functionality: Implements caching for frequently accessed data using Redis.

package main

import (
"encoding/json"
"log"
"net/http"
"github.com/gorilla/mux"
"github.com/go-redis/redis/v8"
"context"
"common/config"
"common/models"
)

var ctx = context.Background()


var rdb *redis.Client

func main() {
config.LoadConfig()

rdb = redis.NewClient(&redis.Options{
Addr: config.Cfg.RedisHost + ":" + config.Cfg.RedisPort,
Password: "", // no password set
DB: 0, // use default DB
})

router := mux.NewRouter()
router.HandleFunc("/cache", SetCache).Methods("POST")
router.HandleFunc("/cache/{key}", GetCache).Methods("GET")

log.Fatal(http.ListenAndServe(":8086", router))
}

func SetCache(w http.ResponseWriter, r *http.Request) {


var cacheRequest models.CacheRequest
json.NewDecoder(r.Body).Decode(&cacheRequest)

err := rdb.Set(ctx, cacheRequest.Key, cacheRequest.Value, 0).Err()


if err != nil {
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
return
}

w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"status": "set"})
}

func GetCache(w http.ResponseWriter, r *http.Request) {


vars := mux.Vars(r)
key := vars["key"]

value, err := rdb.Get(ctx, key).Result()


if err == redis.Nil {
w.WriteHeader(http.StatusNotFound)
return
} else if err != nil {
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
return
}

w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"value": value})
}

Database Models (Common)


Define your database models in the common/models package.

package models

import "gorm.io/gorm"

type User struct {


gorm.Model
Username string `json:"username"`
Password string `json:"password"`
Email string `json:"email"`
}

type Account struct {


gorm.Model
UserID uint `json:"user_id"`
Balance float64 `json:"balance"`
}

type Transaction struct {


gorm.Model
AccountID uint `json:"account_id"`
Amount float64 `json:"amount"`
Type string `json:"type"` // deposit, withdrawal, transfer
}

type PaymentRequest struct {


Amount float64 `json:"amount"`
Currency string `json:"currency"`
Method string `json:"method"`
}

type PaymentResponse struct {


Status string `json:"status"`
TransactionID string `json:"transaction_id"`
}

type ReconciliationRequest struct {


Transactions []Transaction `json:"transactions"`
}

type ReconciliationResult struct {


Status string `json:"status"`
Discrepancies []string `json:"discrepancies"`
}

type Notification struct {


UserID uint `json:"user_id

"`
Message string `json:"message"`
}

type CacheRequest struct {


Key string `json:"key"`
Value string `json:"value"`
}

Deployment
To deploy these services, ensure you have the correct Dockerfiles and Kubernetes manifests.
Here’s a sample Kubernetes manifest for the transaction-service .

Kubernetes Manifest ( deploy/kubernetes/transaction-service-


deployment.yaml )

apiVersion: apps/v1
kind: Deployment
metadata:
name: transaction-service
labels:
app: transaction-service
spec:
replicas: 3
selector:
matchLabels:
app: transaction-service
template:
metadata:
labels:
app: transaction-service
spec:
containers:
- name: transaction-service
image: transaction-service:latest
ports:
- containerPort: 8082
env:
- name: DB_HOST
valueFrom:
configMapKeyRef:
name: db-config
key: db-host
- name: DB_USER
valueFrom:
secretKeyRef:
name: db-secret
key: db-user
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: db-password
---
apiVersion: v1
kind: Service
metadata:
name: transaction-service
spec:
selector:
app: transaction-service
ports:
- protocol: TCP
port: 80
targetPort: 8082

Monitoring and Logging


Integrate tools like Prometheus for monitoring and the ELK stack for logging. Ensure each
service logs significant events and errors to aid debugging and operational monitoring.

Testing
Implement unit tests and integration tests for each service. Use Go’s built-in testing package to
write tests for the business logic.

Integrating Kafka
Kafka can be used for several purposes, including:

1. Event-Driven Architecture: Enable asynchronous communication between services.


2. Transaction Logging: Ensure all transactions are logged for auditing and recovery
purposes.
3. Real-Time Notifications: Process and deliver notifications in real-time.
4. Bank Reconciliation: Stream transaction data to the Bank Reconciliation Service.

Here's how you can incorporate Kafka into your capstone project:

Kafka Setup
1. Kafka Cluster: Set up a Kafka cluster using Docker or Kubernetes.
2. Topics: Define Kafka topics for different types of messages (e.g., transactions,
notifications).

Example Kafka Configuration (docker-compose.yml)


version: '2'
services:
zookeeper:
image: wurstmeister/zookeeper:3.4.6
ports:
- "2181:2181"
kafka:
image: wurstmeister/kafka:latest
ports:
- "9092:9092"
environment:
KAFKA_ADVERTISED_LISTENERS:
INSIDE://kafka:9092,OUTSIDE://localhost:9092
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP:
INSIDE:PLAINTEXT,OUTSIDE:PLAINTEXT
KAFKA_INTER_BROKER_LISTENER_NAME: INSIDE
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
volumes:
- /var/run/docker.sock:/var/run/docker.sock

Using Kafka in Your Services

Transaction Service
This service handles creating transactions and publishing them to Kafka.

Transaction Service Code ( transaction-service/main.go )

package main

import (
"context"
"encoding/json"
"log"
"net/http"

"github.com/gorilla/mux"
"github.com/segmentio/kafka-go"
"common/config"
"common/models"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
var (
db *gorm.DB
kafkaWriter *kafka.Writer
)

func main() {
config.LoadConfig()
dsn := "host=" + config.Cfg.DBHost + " user=" + config.Cfg.DBUser + "
password=" + config.Cfg.DBPassword + " dbname=" + config.Cfg.DBName + "
port=" + config.Cfg.DBPort + " sslmode=disable"
var err error
db, err = gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatal("Failed to connect to database:", err)
}
db.AutoMigrate(&models.Transaction{})

kafkaWriter = &kafka.Writer{
Addr: kafka.TCP("localhost:9092"),
Topic: "transactions",
Balancer: &kafka.LeastBytes{},
}

router := mux.NewRouter()
router.HandleFunc("/transactions", CreateTransaction).Methods("POST")
router.HandleFunc("/transactions/{id}", GetTransaction).Methods("GET")

log.Fatal(http.ListenAndServe(":8082", router))
}

func CreateTransaction(w http.ResponseWriter, r *http.Request) {


var transaction models.Transaction
json.NewDecoder(r.Body).Decode(&transaction)

// Simulate transaction processing logic here


if err := processTransaction(&transaction); err != nil {
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
return
}

db.Create(&transaction)

// Produce message to Kafka


message, _ := json.Marshal(transaction)
kafkaWriter.WriteMessages(r.Context(), kafka.Message{
Key: []byte(transaction.ID.String()),
Value: message,
})

w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(transaction)
}

func GetTransaction(w http.ResponseWriter, r *http.Request) {


vars := mux.Vars(r)
id := vars["id"]
var transaction models.Transaction
db.First(&transaction, id)
if transaction.ID == 0 {
w.WriteHeader(http.StatusNotFound)
return
}
json.NewEncoder(w).Encode(transaction)
}

func processTransaction(transaction *models.Transaction) error {


// Placeholder for real transaction processing logic
return nil
}

Bank Reconciliation Service


This service consumes transaction messages from Kafka and performs reconciliation.

Bank Reconciliation Service Code ( bank-reconciliation-


service/main.go )

package main

import (
"context"
"encoding/json"
"log"
"net/http"

"github.com/gorilla/mux"
"github.com/segmentio/kafka-go"
"common/config"
"common/models"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)

var db *gorm.DB

func main() {
config.LoadConfig()
dsn := "host=" + config.Cfg.DBHost + " user=" + config.Cfg.DBUser + "
password=" + config.Cfg.DBPassword + " dbname=" + config.Cfg.DBName + "
port=" + config.Cfg.DBPort + " sslmode=disable"
var err error
db, err = gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatal("Failed to connect to database:", err)
}
db.AutoMigrate(&models.Transaction{})

go consumeKafkaMessages()

router := mux.NewRouter()
router.HandleFunc("/reconcile", ReconcileTransactions).Methods("POST")

log.Fatal(http.ListenAndServe(":8084", router))
}

func consumeKafkaMessages() {
kafkaReader := kafka.NewReader(kafka.ReaderConfig{
Brokers: []string{"localhost:9092"},
Topic: "transactions",
GroupID: "bank-reconciliation",
})

for {
message, err := kafkaReader.ReadMessage(context.Background())
if err != nil {
log.Fatal("Failed to read message from Kafka:", err)
}

var transaction models.Transaction


if err := json.Unmarshal(message.Value, &transaction); err != nil {
log.Println("Failed to unmarshal transaction:", err)
continue
}

// Store the transaction in the database


db.Create(&transaction)
log.Printf("Processed transaction: %+v", transaction)
}
}

func ReconcileTransactions(w http.ResponseWriter, r *http.Request) {


var reconciliationRequest models.ReconciliationRequest
json.NewDecoder(r.Body).Decode(&reconciliationRequest)

// Simulate bank reconciliation process


reconciliationResult, err := reconcileWithBank(reconciliationRequest)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
return
}

w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(reconciliationResult)
}

func reconcileWithBank(request models.ReconciliationRequest)


(models.ReconciliationResult, error) {
var matchedTransactions []models.Transaction
var discrepancies []string

for _, txn := range request.Transactions {


if matchTransactionWithBank(txn) {
matchedTransactions = append(matchedTransactions, txn)
} else {
discrepancies = append(discrepancies, "Unmatched transaction:
"+txn.ID.String())
}
}

return models.ReconciliationResult{
Status: "reconciled",
Discrepancies: discrepancies,
}, nil
}

func matchTransactionWithBank(txn models.Transaction) bool {


// Placeholder for actual matching logic
// Assume all transactions match for this example
return true
}
How Kafka Fits into the Workflow
1. Transaction Service: When a new transaction is created, it is saved to the database and
published to the transactions Kafka topic.
2. Bank Reconciliation Service: This service listens to the transactions Kafka topic,
consumes new transaction messages, and stores them in its database. This decouples the
transaction processing from the reconciliation process.
3. Reconciliation Process: When a reconciliation request is made, the Bank Reconciliation
Service compares the transactions stored in its database with the provided bank records
and identifies any discrepancies.

Benefits of Using Kafka


Asynchronous Processing: Decouples transaction creation from processing, allowing for
real-time, asynchronous handling.
Scalability: Kafka’s architecture allows you to scale both the producers (Transaction
Service) and consumers (Bank Reconciliation Service) independently.
Reliability: Ensures that all transactions are reliably logged and can be processed even if
some services are temporarily unavailable.

Summary
Transaction Service: Handles transaction creation, saves to the database, and publishes
to Kafka.
Bank Reconciliation Service: Consumes transaction messages from Kafka, stores them,
and handles reconciliation requests.

You might also like