Skip to content

Instantly share code, notes, and snippets.

@evanxg852000
Created August 31, 2023 09:54
Show Gist options
  • Save evanxg852000/4d0441736bd81f1d1bc5d4a817864ed2 to your computer and use it in GitHub Desktop.
Save evanxg852000/4d0441736bd81f1d1bc5d4a817864ed2 to your computer and use it in GitHub Desktop.
Recurce Center pair programming task [Database server]
package main
import (
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
"sync"
"golang.org/x/exp/maps"
)
// error definitions
var (
ioError = errors.New("IoError")
keyNotFound = errors.New("KeyNotFound")
)
// Store interface along with implementations
type Store interface {
Put(key, value string) error
Get(key string) (string, error)
}
type InMemoryStore struct {
data map[string]string
mu sync.RWMutex
}
func (memStore InMemoryStore) Put(key, value string) error {
memStore.mu.Lock()
defer memStore.mu.Unlock()
memStore.data[key] = value
return nil
}
func (memStore InMemoryStore) Get(key string) (string, error) {
memStore.mu.RLock()
defer memStore.mu.RUnlock()
value, ok := memStore.data[key]
if !ok {
return "", keyNotFound
}
return value, nil
}
func NewInMemoryStore() Store {
return InMemoryStore{
data: make(map[string]string),
mu: sync.RWMutex{},
}
}
// TODO: implement a file backed store
// Database server
type DbServer struct {
store Store
}
func NewDbServer(store Store) *DbServer {
return &DbServer{
store: store,
}
}
func (db *DbServer) Set(writer http.ResponseWriter, req *http.Request) {
params := req.URL.Query()
if len(params) != 1 {
writeResponse(writer, http.StatusBadRequest, "error", "Expecting key/value parameters.")
return
}
key := maps.Keys(params)[0]
value := params[key][0] // we could forbid empty values
err := db.store.Put(key, value)
if errors.Is(err, ioError) {
writeResponse(writer, http.StatusInternalServerError, "error", "An io error occurred")
return
}
writeResponse(writer, http.StatusOK, "message", "The key was successfully set.")
}
func (db *DbServer) Get(writer http.ResponseWriter, req *http.Request) {
keySet, ok := req.URL.Query()["key"]
if !ok || len(keySet) != 1 {
writeResponse(writer, http.StatusNotFound, "error", "Expecting one key parameter.")
return
}
key := keySet[0]
value, err := db.store.Get(key)
if errors.Is(err, ioError) {
writeResponse(writer, http.StatusInternalServerError, "error", "An io error occurred.")
return
}
if errors.Is(err, keyNotFound) {
writeResponse(writer, http.StatusNotFound, "error", "The key was not found.")
return
}
writeResponse(writer, http.StatusOK, key, value)
}
func main() {
dbServer := NewDbServer(NewInMemoryStore())
http.HandleFunc("/set", dbServer.Set)
http.HandleFunc("/get", dbServer.Get)
package main
import (
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
"sync"
"golang.org/x/exp/maps"
)
// error definitions
var (
ioError = errors.New("IoError")
keyNotFound = errors.New("KeyNotFound")
)
// Store interface along with implementations
type Store interface {
Put(key, value string) error
Get(key string) (string, error)
}
type InMemoryStore struct {
data map[string]string
mu sync.RWMutex
}
func (memStore InMemoryStore) Put(key, value string) error {
memStore.mu.Lock()
defer memStore.mu.Unlock()
memStore.data[key] = value
return nil
}
func (memStore InMemoryStore) Get(key string) (string, error) {
memStore.mu.RLock()
defer memStore.mu.RUnlock()
value, ok := memStore.data[key]
if !ok {
return "", keyNotFound
}
return value, nil
}
func NewInMemoryStore() Store {
return InMemoryStore{
data: make(map[string]string),
mu: sync.RWMutex{},
}
}
// TODO: implement a file backed store
// Database server
type DbServer struct {
store Store
}
func NewDbServer(store Store) *DbServer {
return &DbServer{
store: store,
}
}
func (db *DbServer) Set(writer http.ResponseWriter, req *http.Request) {
params := req.URL.Query()
if len(params) != 1 {
writeResponse(writer, http.StatusBadRequest, "error", "Expecting key/value parameters.")
return
}
key := maps.Keys(params)[0]
value := params[key][0] // we could forbid empty values
err := db.store.Put(key, value)
if errors.Is(err, ioError) {
writeResponse(writer, http.StatusInternalServerError, "error", "An io error occurred")
return
}
writeResponse(writer, http.StatusOK, "message", "The key was successfully set.")
}
func (db *DbServer) Get(writer http.ResponseWriter, req *http.Request) {
keySet, ok := req.URL.Query()["key"]
if !ok || len(keySet) != 1 {
writeResponse(writer, http.StatusNotFound, "error", "Expecting one key parameter.")
return
}
key := keySet[0]
value, err := db.store.Get(key)
if errors.Is(err, ioError) {
writeResponse(writer, http.StatusInternalServerError, "error", "An io error occurred.")
return
}
if errors.Is(err, keyNotFound) {
writeResponse(writer, http.StatusNotFound, "error", "The key was not found.")
return
}
writeResponse(writer, http.StatusOK, key, value)
}
func main() {
dbServer := NewDbServer(NewInMemoryStore())
http.HandleFunc("/set", dbServer.Set)
http.HandleFunc("/get", dbServer.Get)
fmt.Println("Server running at http://localhost:4000")
http.ListenAndServe(":4000", nil)
}
// helpers
func writeResponse(writer http.ResponseWriter, statusCode int, key, value string) {
writer.WriteHeader(statusCode)
writer.Header().Set("Content-Type", "application/json")
resp := map[string]string{key: value}
data, err := json.Marshal(resp)
if err != nil {
log.Fatalf("Marshaling error. Err: %s", err)
}
writer.Write(data)
}
fmt.Println("Server running at http://localhost:4000")
http.ListenAndServe(":4000", nil)
}
// helpers
func writeResponse(writer http.ResponseWriter, statusCode int, key, value string) {
writer.WriteHeader(statusCode)
writer.Header().Set("Content-Type", "application/json")
resp := map[string]string{key: value}
data, err := json.Marshal(resp)
if err != nil {
log.Fatalf("Marshaling error. Err: %s", err)
}
writer.Write(data)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment