diff --git a/coverage/coverage.html b/coverage/coverage.html index b698bde..097da85 100644 --- a/coverage/coverage.html +++ b/coverage/coverage.html @@ -144,6 +144,8 @@
diff --git a/internal/domain/service/GetDriversByRace.go b/internal/domain/service/GetDriversByRace.go index 8083609..1766cb8 100644 --- a/internal/domain/service/GetDriversByRace.go +++ b/internal/domain/service/GetDriversByRace.go @@ -1,6 +1,8 @@ package domain_service import ( + "sync" + domain_model "github.com/mmadariaga/go-api/internal/domain/model" ) @@ -8,18 +10,68 @@ type GetDriversByRaceDependencies interface { FetchDriversByRace(raceId int) ([]domain_model.Driver, error) } +type fetchDriversResponse struct { + raceId int + response []domain_model.Driver + error error +} + func GetDriversByRace(races []domain_model.Race, dependencies GetDriversByRaceDependencies) (map[int][]domain_model.Driver, error) { driversByRace := make(map[int][]domain_model.Driver) + var waitGroup sync.WaitGroup + respChannel := make(chan fetchDriversResponse, 8) + for _, race := range races { - result, error := dependencies.FetchDriversByRace(race.Id) - if error != nil { - return nil, error + waitGroup.Add(1) + go fetchDrivers( + race.Id, + dependencies, + respChannel, + &waitGroup, + ) + } + + go func() { + waitGroup.Wait() + close(respChannel) + }() + + for resp := range respChannel { + + if resp.error != nil { + return nil, resp.error } - driversByRace[race.Id] = result + driversByRace[resp.raceId] = resp.response } return driversByRace, nil } + +func fetchDrivers( + raceId int, + dependencies GetDriversByRaceDependencies, + respChan chan<- fetchDriversResponse, + waitGroup *sync.WaitGroup, +) { + defer waitGroup.Done() + + result, error := dependencies.FetchDriversByRace(raceId) + + if error != nil { + respChan <- fetchDriversResponse{ + raceId: raceId, + response: nil, + error: error, + } + return + } + + respChan <- fetchDriversResponse{ + raceId: raceId, + response: result, + error: nil, + } +} diff --git a/internal/domain/service/GetPodiumByRace.go b/internal/domain/service/GetPodiumByRace.go index d3caa2a..9c12a80 100644 --- a/internal/domain/service/GetPodiumByRace.go +++ b/internal/domain/service/GetPodiumByRace.go @@ -1,6 +1,8 @@ package domain_service import ( + "sync" + domain_model "github.com/mmadariaga/go-api/internal/domain/model" ) @@ -9,6 +11,12 @@ type GetPodiumByRaceDependencies interface { FetchPodiumByRace(raceId int, drivers []domain_model.Driver) ([3]domain_model.Podium, error) } +type fetchPodiumsResponse struct { + raceId int + response [3]domain_model.Podium + error error +} + func GetPodiumByRace( races []domain_model.Race, dependencies GetPodiumByRaceDependencies, @@ -21,14 +29,59 @@ func GetPodiumByRace( return nil, error } + var waitGroup sync.WaitGroup + respChannel := make(chan fetchPodiumsResponse, 8) for _, race := range races { - result, error := dependencies.FetchPodiumByRace(race.Id, drivers[race.Id]) - if error != nil { - return nil, error + waitGroup.Add(1) + go fetchPodiums( + race.Id, + drivers[race.Id], + dependencies, + respChannel, + &waitGroup, + ) + } + + go func() { + waitGroup.Wait() + close(respChannel) + }() + + for resp := range respChannel { + + if resp.error != nil { + return nil, resp.error } - podiumsByRace[race.Id] = result + podiumsByRace[resp.raceId] = resp.response } return podiumsByRace, nil } + +func fetchPodiums( + raceId int, + drivers []domain_model.Driver, + dependencies GetPodiumByRaceDependencies, + respChan chan<- fetchPodiumsResponse, + waitGroup *sync.WaitGroup, +) { + defer waitGroup.Done() + + result, error := dependencies.FetchPodiumByRace(raceId, drivers) + + if error != nil { + respChan <- fetchPodiumsResponse{ + raceId: raceId, + response: [3]domain_model.Podium{}, + error: error, + } + return + } + + respChan <- fetchPodiumsResponse{ + raceId: raceId, + response: result, + error: nil, + } +} diff --git a/internal/infrastructure/service/FetchDriversByRace.go b/internal/infrastructure/service/FetchDriversByRace.go index b53fab3..9fff0bd 100644 --- a/internal/infrastructure/service/FetchDriversByRace.go +++ b/internal/infrastructure/service/FetchDriversByRace.go @@ -16,7 +16,10 @@ type Driver = domain_model.Driver func FetchDriversByRace(raceId int) ([]Driver, error) { targetUrl := "https://api.openf1.org/v1/drivers?session_key=" + strconv.Itoa(raceId) - body := helper.HttpGet(targetUrl, true) + body, err := helper.HttpGet(targetUrl, true) + if err != nil { + return nil, err + } // JSON to struct var drivers []struct { diff --git a/internal/infrastructure/service/FetchPodiumByRace.go b/internal/infrastructure/service/FetchPodiumByRace.go index d6c041f..4e4e12d 100644 --- a/internal/infrastructure/service/FetchPodiumByRace.go +++ b/internal/infrastructure/service/FetchPodiumByRace.go @@ -21,7 +21,10 @@ type Position struct { func FetchPodiumByRace(raceId int, drivers []domain_model.Driver) ([3]Podium, error) { targetUrl := "https://api.openf1.org/v1/position?position<=3&session_key=" + strconv.Itoa(raceId) - body := helper.HttpGet(targetUrl, true) + body, err := helper.HttpGet(targetUrl, true) + if err != nil { + return [3]Podium{}, err + } // JSON to struct var positions []Position diff --git a/internal/infrastructure/service/FetchRacesByYear.go b/internal/infrastructure/service/FetchRacesByYear.go index a1cd087..992d541 100644 --- a/internal/infrastructure/service/FetchRacesByYear.go +++ b/internal/infrastructure/service/FetchRacesByYear.go @@ -17,7 +17,10 @@ type Race = domain_model.Race func FetchRacesByYear(year int) ([]Race, error) { targetUrl := "https://api.openf1.org/v1/sessions?year=" + strconv.Itoa(year) + "&session_type=Race" - body := helper.HttpGet(targetUrl, true) + body, err := helper.HttpGet(targetUrl, true) + if err != nil { + return nil, err + } // JSON to struct var sessions []struct { diff --git a/internal/infrastructure/service/helper/HttpGet.go b/internal/infrastructure/service/helper/HttpGet.go index 5de8d54..aa33b5f 100644 --- a/internal/infrastructure/service/helper/HttpGet.go +++ b/internal/infrastructure/service/helper/HttpGet.go @@ -1,9 +1,11 @@ package infrastructure_service_helper import ( + "errors" "fmt" "io" "net/http" + "sync" "time" "github.com/rs/zerolog/log" @@ -15,24 +17,28 @@ type cacheValue struct { } var reqCache = make(map[string]cacheValue) +var rwMutex sync.RWMutex -func HttpGet(targetUrl string, useCache bool) []byte { +func HttpGet(targetUrl string, useCache bool) ([]byte, error) { + rwMutex.RLock() cachedValue, isCached := reqCache[targetUrl] + rwMutex.RUnlock() now := time.Now().Unix() useCachedValue := useCache && isCached && now <= cachedValue.validUntil if useCachedValue { - return cachedValue.value + return cachedValue.value, nil } // Fetch data resp, err := http.Get(targetUrl) if err != nil || resp.StatusCode < 200 || resp.StatusCode >= 300 { - log.Panic().Msg( - fmt.Sprintf("Error al hacer la peticiĆ³n: %v en %v", err, targetUrl), - ) + errorMsg := fmt.Sprintf("Request error: %v at %v", err, targetUrl) + log.Error().Msg(errorMsg) + + return nil, errors.New(errorMsg) } defer resp.Body.Close() @@ -40,18 +46,23 @@ func HttpGet(targetUrl string, useCache bool) []byte { // Extract body body, err := io.ReadAll(resp.Body) if err != nil { - log.Panic().Msg( - fmt.Sprintf("Error al leer el cuerpo de la respuesta: %v", err), - ) + errorMsg := fmt.Sprintf("Error reading response body: %v", err) + log.Error().Msg(errorMsg) + + return nil, errors.New(errorMsg) } if useCache { tenMinuteAfter := time.Now().Add(10 * time.Minute).Unix() + + rwMutex.Lock() reqCache[targetUrl] = cacheValue{ validUntil: tenMinuteAfter, value: body, } + rwMutex.Unlock() + } - return body + return body, nil }