This is a microservice designed to manage access to users, implemented in Go. The service provides both HTTP and gRPC APIs for interacting with user data stored in a MongoDB database.
- Add a new User
- Modify an existing User
- Remove a User
- Return a paginated list of Users with filtering capabilities
- Notify other services of changes to User Entities
- Health checks
- Go (version 1.18 or later)
- MongoDB
- Docker (for running MongoDB locally)
- Clone the repository: (Or extract the .zip file given) - The repo below is private
git clone https://github.com/HaydnG/userapi
cd userapi
- Install dependencies:
go mod tidy
- Ensure MongoDB is running. If you have Docker installed, you can run MongoDB using:
#This will expose a mongoDB server hosted at `:27017`
docker-compose up --build
- Start the HTTP and gRPC servers:
go run userapi.go -httpport=8080 -grpcport=9090
protoc --go_out=. --go-grpc_out=. pb/user.proto
This will generate files into the pb directory
go test ./... --cover -count=1
? userapi/db [no test files]
? userapi/health [no test files]
? userapi/mocks [no test files]
? userapi/pb [no test files]
ok userapi 0.472s coverage: 74.4% of statements
ok userapi/cacheStore 2.191s coverage: 83.3% of statements
ok userapi/data 0.222s coverage: [no statements]
ok userapi/validation 0.190s coverage: 100.0% of statements
pkg: userapi/validation
cpu: AMD Ryzen 7 5800X3D 8-Core Processor
BenchmarkNumber-16 46102778 25.43 ns/op 0 B/op 0 allocs/op
BenchmarkUser-16 3496152 342.2 ns/op 0 B/op 0 allocs/op
PASS
ok userapi/validation 2.900s
TODO Measure http/gRPC handler performance
- GET /userapi/getall: Fetches all users. (20 sec cache)
- GET /userapi/get: Finds users with a given query.
- Query parameters:
country
,nickname
,createdAfter
,page
,limit
.
- Query parameters:
- POST /userapi/add: Creates a new user.
- POST /userapi/update: Updates an existing user.
- POST /userapi/delete: Deletes a user by ID.
- GET /userapi/deleteall: Deletes all users.
- GET /healthz: Health check endpoint for both HTTP and gRPC servers.
- Expected response (STATUS_OK 200)
- Failure response (STATUS_InternalServerError 500)
curl 'http://localhost:8080/userapi/add' \
-H 'Content-Type: application/json' \
--data-raw '{
"first_name": "Razzil",
"last_name": "Darkbrew",
"nickname": "Alchemist",
"password": "moneyMoneyM0n3y",
"email": "[email protected]",
"country": "UK"
}'
Example AddUser Response
{
"id": "0d0f9944-d902-4db1-b83b-6b25a61f89e2",
"first_name": "Razzil",
"last_name": "Darkbrew",
"nickname": "Alchemist",
"password": "moneyMoneyM0n3y",
"email": "[email protected]",
"country": "UK",
"created_at": "2024-06-16T17:32:28.2136171Z",
"updated_at": "2024-06-16T17:32:28.2136171Z"
}
- Expected response (STATUS_OK 200)
- Failure response (STATUS_InternalServerError 500)
curl 'http://localhost:8080/userapi/update' \
-H 'Content-Type: application/json' \
--data-raw '{
"ID": "$(id from addUser)",
"first_name": "Razzil",
"last_name": "Darkbrew",
"nickname": "Alchemist",
"password": "Blink!moneyMoneyM0n3y",
"email": "[email protected]",
"country": "UK"
}'
Example UpdateUser Response
{
"id": "0d0f9944-d902-4db1-b83b-6b25a61f89e2",
"first_name": "Razzil",
"last_name": "Darkbrew",
"nickname": "Alchemist",
"password": "Blink!moneyMoneyM0n3y",
"email": "[email protected]",
"country": "UK",
"created_at": "2024-06-16T17:32:28.213Z",
"updated_at": "2024-06-16T17:43:38.985Z"
}
- Expected response (STATUS_OK 200)
- Failure response (STATUS_InternalServerError 500)
curl 'http://localhost:8080/userapi/getall'
Example UpdateUser Response
[
{
"id": "0d0f9944-d902-4db1-b83b-6b25a61f89e2",
"first_name": "Razzil",
"last_name": "Darkbrew",
"nickname": "Alchemist",
"password": "Blink!moneyMoneyM0n3y",
"email": "[email protected]",
"country": "UK",
"created_at": "2024-06-16T17:32:28.213Z",
"updated_at": "2024-06-16T17:43:38.985Z"
},
{
"id": "a5557cd5-3083-4ecb-a888-71d98ee1e39e",
"first_name": "Visage",
"last_name": "joe",
"nickname": "aXE",
"password": "VERYSEcure3343",
"email": "[email protected]",
"country": "UK",
"created_at": "2024-06-16T17:46:01.377Z",
"updated_at": "2024-06-16T17:46:01.377Z"
}
]
- Expected response (STATUS_OK 200)
- Failure response (STATUS_InternalServerError 500)
curl 'http://localhost:8080/userapi/get?country=UK&nickname=al&createdAfter=2024-06-14T18%3A37%3A47.572Z&page=1&limit=50'
Example GetUsers Response
[
{
"id": "0d0f9944-d902-4db1-b83b-6b25a61f89e2",
"first_name": "Razzil",
"last_name": "Darkbrew",
"nickname": "Alchemist",
"password": "Blink!moneyMoneyM0n3y",
"email": "[email protected]",
"country": "UK",
"created_at": "2024-06-16T17:32:28.213Z",
"updated_at": "2024-06-16T17:43:38.985Z"
}
]
- Expected response (STATUS_OK 200)
- Failure response (STATUS_InternalServerError 500)
curl --location 'http://localhost:8080/userapi/delete' \
-H 'Content-Type: application/json' \
--data-raw '{
"ID": "d174809b-5559-4855-a74b-ddf24e993e39"
}'
- Expected response (STATUS_OK 200)
- Failure response (STATUS_InternalServerError 500)
curl --location 'http://localhost:8080/userapi/deleteall' -H 'Content-Type: application/json'
- UserService.GetAllUsers: Fetches all users. (20 sec cache)
- UserService.GetUsers: Finds users with a given query.
- UserService.AddUser: Creates a new user.
- UserService.UpdateUser: Updates an existing user.
- UserService.DeleteUser: Deletes a user by ID.
user.UserService is a service:
service UserService {
rpc AddUser ( .user.AddUserRequest ) returns ( .user.User );
rpc DeleteUser ( .user.DeleteUserRequest ) returns ( .user.Empty );
rpc GetAllUsers ( .google.protobuf.Empty ) returns ( .user.GetUsersResponse );
rpc GetUsers ( .user.GetUsersRequest ) returns ( .user.GetUsersResponse );
rpc UpdateUser ( .user.UpdateUserRequest ) returns ( .user.User );
}
You can use grpcurl
to interact with the gRPC service. Here are some examples:
To listen for any user changes, you can use the following:
grpcurl -plaintext -d '{}' localhost:9090 user.UserService/WatchUsers
Example Watcher Output (Add + Deletion)
{
"userId": "fabf2700-3711-45aa-a4c1-aa479b8ec95d",
"updateType": "CREATED",
"user": {
"ID": "fabf2700-3711-45aa-a4c1-aa479b8ec95d",
"firstName": "Visage",
"lastName": "joe",
"nickname": "aXE",
"password": "VERYSEcure3343",
"email": "[email protected]",
"country": "UK",
"createdAt": "2024-06-18T19:34:18.404692100Z",
"updatedAt": "2024-06-18T19:34:18.404692100Z"
}
}
{
"userId": "fabf2700-3711-45aa-a4c1-aa479b8ec95d",
"updateType": "DELETED",
"user": {
"ID": "fabf2700-3711-45aa-a4c1-aa479b8ec95d"
}
}
grpcurl -plaintext -d '{
"first_name": "Razzil",
"last_name": "Darkbrew",
"nickname": "Alchemist",
"password": "moneyMoneyM0n3y",
"email": "[email protected]",
"country": "UK"
}' localhost:9090 user.UserService/AddUser
Example AddUser Response
{
"ID": "ab64160e-5093-442e-b131-fd6ea3d3b17d",
"firstName": "Razzil",
"lastName": "Darkbrew",
"nickname": "Alchemist",
"password": "moneyMoneyM0n3y",
"email": "[email protected]",
"country": "UK",
"createdAt": "2024-06-16T17:19:01.140270700Z",
"updatedAt": "2024-06-16T17:19:01.140270700Z"
}
grpcurl -plaintext -d '{
"ID": "$(id from addUser)",
"first_name": "Razzil",
"last_name": "Darkbrew",
"nickname": "Alchemist",
"password": "Blink!moneyMoneyM0n3y",
"email": "[email protected]",
"country": "UK"
}' localhost:9090 user.UserService/UpdateUser
Example UpdateUser Response
{
"ID": "ab64160e-5093-442e-b131-fd6ea3d3b17d",
"firstName": "Razzil",
"lastName": "Darkbrew",
"nickname": "Alchemist",
"password": "Blink!moneyMoneyM0n3y",
"email": "[email protected]",
"country": "UK",
"createdAt": "2024-06-16T17:19:01.140Z",
"updatedAt": "2024-06-16T17:21:49.086Z"
}
grpcurl -plaintext localhost:9090 user.UserService/GetAllUsers
Example GetAllUsers Response
{
"users": [
{
"ID": "8c358ed6-adbf-4756-ae97-0f68c8f16876",
"firstName": "Visage",
"lastName": "joe",
"nickname": "aXE",
"password": "VERYSEcure3343",
"email": "[email protected]",
"country": "UK",
"createdAt": "2024-06-16T17:08:10.299Z",
"updatedAt": "2024-06-16T17:08:10.299Z"
},
{
"ID": "ab64160e-5093-442e-b131-fd6ea3d3b17d",
"firstName": "Razzil",
"lastName": "Darkbrew",
"nickname": "Alchemist",
"password": "Blink!moneyMoneyM0n3y",
"email": "[email protected]",
"country": "UK",
"createdAt": "2024-06-16T17:19:01.140Z",
"updatedAt": "2024-06-16T17:21:49.086Z"
}
]
}
grpcurl -plaintext -d '{
"country": "UK",
"nickname": "",
"created_after": "2024-06-15T17:19:01.140Z",
"page": "1",
"limit": "50"
}' localhost:9090 user.UserService/GetUsers
Example GetAllUsers Response
{
"users": [
{
"ID": "8c358ed6-adbf-4756-ae97-0f68c8f16876",
"firstName": "Visage",
"lastName": "joe",
"nickname": "aXE",
"password": "VERYSEcure3343",
"email": "[email protected]",
"country": "UK",
"createdAt": "2024-06-16T17:08:10.299Z",
"updatedAt": "2024-06-16T17:08:10.299Z"
},
{
"ID": "ab64160e-5093-442e-b131-fd6ea3d3b17d",
"firstName": "Razzil",
"lastName": "Darkbrew",
"nickname": "Alchemist",
"password": "Blink!moneyMoneyM0n3y",
"email": "[email protected]",
"country": "UK",
"createdAt": "2024-06-16T17:19:01.140Z",
"updatedAt": "2024-06-16T17:21:49.086Z"
}
]
}
grpcurl -plaintext -d '{"ID": "8c358ed6-adbf-4756-ae97-0f68c8f16876"}' localhost:9090 user.UserService/DeleteUser
The main file sets up and starts the HTTP and gRPC servers, and handles graceful shutdown.
getAllUsersHandler
: Fetches all users from the database.getUserHandler
: Finds users based on query parameters.addUserHandler
: Adds a new user to the database.updateUserHandler
: Updates an existing user in the database.deleteUserHandler
: Deletes a user by ID.deleteAllUsersHandler
: Deletes all users from the database.
ServiceServer.GetAllUsers
: Fetches all users from the database.ServiceServer.GetUsers
: Finds users based on query parameters.ServiceServer.AddUser
: Adds a new user to the database.ServiceServer.UpdateUser
: Updates an existing user in the database.ServiceServer.DeleteUser
: Deletes a user by ID.
The health check handler ensures that both HTTP and gRPC servers are ready to serve requests.