Skip to content

Commit a03dc34

Browse files
Merge pull request #55 from robertoduessmann/cache
Implement caching
2 parents d9500e3 + 4847d83 commit a03dc34

File tree

2 files changed

+71
-44
lines changed

2 files changed

+71
-44
lines changed

cache/manager.go

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,36 +7,48 @@ import (
77

88
type cacheManager struct {
99
avaliablesCache map[string]CacheClient
10+
mu sync.RWMutex
1011
}
1112

12-
var instance CacheManager = nil
13+
var (
14+
instance CacheManager
15+
once sync.Once
16+
)
1317

1418
func NewCacheManager() CacheManager {
15-
sync.OnceFunc(func() {
16-
if instance == nil {
17-
instance = cacheManager{
18-
avaliablesCache: make(map[string]CacheClient),
19-
}
19+
once.Do(func() {
20+
instance = &cacheManager{
21+
avaliablesCache: make(map[string]CacheClient),
2022
}
21-
})()
23+
})
2224
return instance
2325
}
2426

25-
func (m cacheManager) NewCache(name string, ttl time.Duration) CacheClient {
27+
func (m *cacheManager) NewCache(name string, ttl time.Duration) CacheClient {
28+
m.mu.RLock()
2629
client, found := m.avaliablesCache[name]
30+
m.mu.RUnlock()
2731
if found {
2832
return client
2933
}
3034

35+
m.mu.Lock()
36+
defer m.mu.Unlock()
3137
cacheClient := NewCacheClient(ttl)
3238
m.avaliablesCache[name] = cacheClient
3339
return cacheClient
3440
}
3541

36-
func (m cacheManager) Erase(name string) bool {
42+
func (m *cacheManager) Erase(name string) bool {
43+
m.mu.Lock()
44+
defer m.mu.Unlock()
45+
if _, exists := m.avaliablesCache[name]; exists {
46+
delete(m.avaliablesCache, name)
47+
return true
48+
}
3749
return false
3850
}
3951

40-
func (m cacheManager) Delete(name string) bool {
41-
return false
52+
func (m *cacheManager) Delete(name string) bool {
53+
return m.Erase(name)
4254
}

controller/v2/weather.go

Lines changed: 48 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ package v2
33
import (
44
"encoding/json"
55
"fmt"
6+
"log"
67
"net/http"
78
"time"
89

910
"github.com/gorilla/mux"
11+
"github.com/robertoduessmann/weather-api/cache"
1012
"github.com/robertoduessmann/weather-api/model"
1113
)
1214

@@ -15,60 +17,70 @@ const (
1517
wttrURL = "https://wttr.in"
1618
)
1719

18-
// CurrentWeather gets the current weather to show in JSON format
19-
//
20-
// This endpoint uses wttr API with JSON response under the hood to make it
21-
// easier to handle with units and formats
20+
func writeError(w http.ResponseWriter, status int, message string) {
21+
w.Header().Set("Content-Type", "application/json")
22+
w.WriteHeader(status)
23+
json.NewEncoder(w).Encode(model.ErrorMessage{Message: message})
24+
}
25+
2226
func CurrentWeather(w http.ResponseWriter, r *http.Request) {
2327
city := mux.Vars(r)["city"]
28+
unit := mux.Vars(r)["unit"]
29+
cacheKey := fmt.Sprintf("%s-%s", city, unit)
30+
31+
cacheManager := cache.NewCacheManager()
32+
weatherCache := cacheManager.NewCache("weather", 10*time.Minute)
33+
34+
if cached, found := weatherCache.Get(cacheKey); found {
35+
log.Printf("[CACHE HIT] key=%s", cacheKey)
36+
w.Header().Set("Content-Type", "application/json")
37+
w.Write(cached.([]byte))
38+
return
39+
}
40+
41+
log.Printf("[CACHE MISS] key=%s", cacheKey)
2442
uri := fmt.Sprintf("%s/%s?format=j1", wttrURL, city)
2543
res, err := http.Get(uri)
26-
2744
if err != nil {
28-
errJSON, _ := json.Marshal(model.ErrorMessage{Message: err.Error()})
29-
w.WriteHeader(http.StatusInternalServerError)
30-
w.Header().Set("Content-Type", "application/json")
31-
w.Write(errJSON)
45+
writeError(w, http.StatusInternalServerError, err.Error())
3246
return
3347
}
48+
defer res.Body.Close()
3449

3550
if res.StatusCode != http.StatusOK {
36-
errJSON, _ := json.Marshal(model.ErrorMessage{Message: "NOT_FOUND"})
37-
w.WriteHeader(http.StatusNotFound)
38-
w.Header().Set("Content-Type", "application/json")
39-
w.Write(errJSON)
51+
writeError(w, http.StatusNotFound, "NOT_FOUND")
4052
return
4153
}
4254

43-
defer res.Body.Close()
44-
4555
var wttr wttrResponse
4656
if err := json.NewDecoder(res.Body).Decode(&wttr); err != nil {
47-
errJSON, _ := json.Marshal(model.ErrorMessage{Message: err.Error()})
48-
w.WriteHeader(http.StatusInternalServerError)
49-
w.Header().Set("Content-Type", "application/json")
50-
w.Write(errJSON)
57+
writeError(w, http.StatusInternalServerError, err.Error())
5158
return
5259
}
5360

54-
var (
55-
cc = wttr.CurrentCondition[0]
56-
unit = mux.Vars(r)["unit"]
57-
)
61+
if len(wttr.CurrentCondition) == 0 || len(wttr.CurrentCondition[0].WeatherDesc) == 0 {
62+
writeError(w, http.StatusInternalServerError, "Unexpected API response")
63+
return
64+
}
5865

66+
cc := wttr.CurrentCondition[0]
5967
response := model.Weather{
6068
Description: cc.WeatherDesc[0].Value,
6169
Temperature: cc.Temp(unit),
6270
Wind: cc.Windspeed(unit),
71+
Forecast: [3]model.Forecast{},
6372
}
6473

65-
for i, weather := range wttr.Weather {
74+
for i := 0; i < len(wttr.Weather) && i < 3; i++ {
75+
weather := wttr.Weather[i]
76+
if len(weather.Hourly) == 0 {
77+
writeError(w, http.StatusInternalServerError, "Incomplete forecast data")
78+
return
79+
}
80+
6681
day, err := time.Parse(timeFormat, weather.Date)
6782
if err != nil {
68-
errJSON, _ := json.Marshal(model.ErrorMessage{Message: err.Error()})
69-
w.WriteHeader(http.StatusInternalServerError)
70-
w.Header().Set("Content-Type", "application/json")
71-
w.Write(errJSON)
83+
writeError(w, http.StatusInternalServerError, err.Error())
7284
return
7385
}
7486

@@ -79,11 +91,14 @@ func CurrentWeather(w http.ResponseWriter, r *http.Request) {
7991
}
8092
}
8193

82-
if err := json.NewEncoder(w).Encode(response); err != nil {
83-
errJSON, _ := json.Marshal(model.ErrorMessage{Message: err.Error()})
84-
w.WriteHeader(http.StatusInternalServerError)
85-
w.Header().Set("Content-Type", "application/json")
86-
w.Write(errJSON)
94+
encoded, err := json.Marshal(response)
95+
if err != nil {
96+
writeError(w, http.StatusInternalServerError, err.Error())
8797
return
8898
}
99+
100+
weatherCache.Put(cacheKey, encoded)
101+
102+
w.Header().Set("Content-Type", "application/json")
103+
w.Write(encoded)
89104
}

0 commit comments

Comments
 (0)