Create a service that will store client events (title, description, point of time (date and time in certain timezone) of start, duration, notes [optional])
- GET events (with filter by date/data-range, time range, title),
- GET event by ID,
- POST create an event,
- PUT update an event (title, description, data & time, timezone, duration, notes),
- DELETE delete event.
swagger: "3.9"
info:
title: Calendar
version: '1.0'
description: Simple event calendar server
license:
name: Open license
host: 'localhost:5000'
basePath: "/"
tags:
- name: auth
description: Basic auth operations
- name: user
description: Basic user operations
- name: events
description: Basic events operations
- name: event
description: Basic event operations
schemas: "http"
paths:
/login:
post:
tags:
- auth
summary: Logs user into the system
description: ''
consumes:
- application/json
parameters:
- in: body
name: body
description: Auth form data as json
required: true
schema:
$ref: '#/definitions/Auth'
responses:
'200':
description: Successfully saved"
/logout:
get:
tags:
- auth
summary: Logs out current logged in user session
description: ''
operationId: logoutUser
produces:
- text/plain
parameters: []
responses:
default:
description: successful loged out
/api/user:
put:
tags:
- user
summary: Update user's timezone
description: 'This operation can be done only for loged in users'
consumes:
- application/json
parameters:
- in: body
name: body
description: User's timezone data
required: true
schema:
$ref: '#/definitions/User'
responses:
'401':
description: Unathorized access
'200':
description: Successfully saved
/api/events:
get:
tags:
- events
summary: Get events
description: 'This operation can be done only for loged in users'
consumes:
- application/json
parameters:
- name: title
in: query
description: The title of the event
required: false
type: string
default: birthday
- name: timezone
in: query
description: The timezone of events
required: false
type: string
default: Europe/Riga
- name: dateFrom
in: query
description: Events must be after or on this date
required: false
type: string
format: date
default: '2021-09-01'
- name: dateTo
in: query
description: Events must be before this date
required: false
type: string
format: date
default: '2021-09-01'
- name: timeFrom
in: query
description: Events must be after or on this time
required: false
type: string
format: time
default: 08:00
- name: timeTo
in: query
description: Events must be before this time
required: false
type: string
format: time
default: 10:00
responses:
'401':
description: Unathorized access
'200':
description: Successful operation
schema:
type: array
items:
$ref: '#/definitions/Event'
post:
tags:
- event
summary: Create event
description: 'This operation can be done only for loged in users'
consumes:
- application/json
parameters:
- in: body
name: body
description: Created event object
required: true
schema:
$ref: '#/definitions/Event'
responses:
'401':
description: Unathorized access
'201':
description: Successfully saved
/api/event/{id}:
get:
tags:
- event
summary: Get event by id
description: 'This operation can be done only for loged in users'
parameters:
- name: id
in: path
description: 'Event ID'
required: true
type: integer
default: 1
responses:
'401':
description: Unathorized access
'200':
description: Successful operation
schema:
$ref: '#/definitions/Event'
put:
tags:
- event
summary: Update event
description: 'This operation can be done only for loged in users'
consumes:
- application/json
parameters:
- in: body
name: body
description: Updated event object
required: true
schema:
$ref: '#/definitions/Event'
responses:
'401':
description: Unathorized access
'201':
description: Successfully saved
definitions:
Auth:
type: object
properties:
Username:
type: string
Password:
type: string
User:
type: object
properties:
login:
type: string
timezone:
type: string
Event:
type: object
properties:
id:
type: string
title:
type: string
description:
type: string
time:
type: string
timezone:
type: string
duration:
type: integer
format: int32
notes:
type: array
items:
type: string
- Start from utilizing standard packages:
net/http
; - Use
net/http/httptest
for creation tests; - Fineout how to convert & store time between different timezones with package
time
; - All data should be stored in memory;
- Use Postman to validate your server;
- Use is and moq in tests;
package main
import "fmt"
import "time"
func main() {
loc, err := time.LoadLocation("America/Chicago")
if err != nil {
panic(err)
}
t := time.Now().In(loc)
fmt.Println(t)
}
Store events per user. For this you require to have authentication/authorization; User should have own time zone and all events should respect it by default.
- Add login/logout enpoints (credantials should be stored in memory as well);
- Use JWT for authorization at other endpoints;
- Update logic to Events endpoints to authorize access only to related to user events.
- PUT for update user timezone; Time of events should be returned in a new timezone.
For preparing service to the production lunch it should have:
- good test coverage: 60-70% of unit tests;
- collect metrics for observability of it health;
- structured logs for investigation; information from logs should be enough for troubleshooting;
- Add system endpoints for return metrics;
- served in a separate port;
- metrics:
- total number of events,
- number of users,
- requests per second,
- avg. requests per user per second,
- goroutines used,
- memory used,
- cpu used;
- Organize load testing with
vegetta
; - Add support of gracefull shutdown;
- Check tests coverage, add tests if it low;
- Use structured logs (with tabs; key: value format);
- Add enough context information to the error message/logs;
- Graceful shutdown
- tests coverage
- structured logging
- logs for HTTP service
- load testing with vegeta
- example of Prometheus metrics
Add persistence layer. Now all information about events and user should be stored inside database.
- Organize DB connection (learn what parameters it accept and what of them should be used), make it configurable; (use Docker to start PostgreSQL)
- Use DB migration to setup schema on the start;
- Update logic to store data in the DB;
- Add integration test to cover interation with a DB;
docker-compose up -d db # for start DB, all initial queries should be in initdb.d folder
docker-compose logs -f db #for reading logs
dc exec db /bin/sh # enter DB container
psql --host=db --username=gouser --dbname=gotest # launch PG client
psql -U postgres # as superuser, alternative variant
Provide same API with gRPC.
- define protobuff for your task and user models
- define protobuff definition for your Task APIs
- use protoc tool to generate grpc stubs
- define auth interceptor (gRPC middleware) that will reuse same logic from http auth md
- add second main file that will serve only gRPC requests
- write gRPC client that will call one of your APIs
- install BloomRPC (postman analog for gRPC) to test your APIs