diff --git a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/CustomersServiceClient.java b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/CustomersServiceClient.java index 3f27a7ae4b8c26f25d94b34c4c12f4900a9ed1ff..2aaa6c67463527a87a941e0f7928cea3d996b42c 100644 --- a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/CustomersServiceClient.java +++ b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/CustomersServiceClient.java @@ -16,7 +16,7 @@ package org.springframework.samples.petclinic.api.application; import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.samples.petclinic.api.dto.OwnerDetails; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; diff --git a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/VisitsServiceClient.java b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/VisitsServiceClient.java index 1ac20feb6a1f58fdcc341d3ab414133510c0f8b9..19911796b85210f9d3a649f265e3427c8a276ca7 100644 --- a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/VisitsServiceClient.java +++ b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/VisitsServiceClient.java @@ -15,17 +15,19 @@ */ package org.springframework.samples.petclinic.api.application; +import java.util.List; +import java.util.Map; import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.http.HttpMethod; +import org.springframework.samples.petclinic.api.dto.VisitDetails; +import org.springframework.samples.petclinic.api.dto.Visits; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; -import java.util.List; -import java.util.Map; import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.joining; +import static org.springframework.web.util.UriComponentsBuilder.fromHttpUrl; /** * @author Maciej Szarlinski @@ -36,13 +38,17 @@ public class VisitsServiceClient { private final RestTemplate loadBalancedRestTemplate; - public Map<Integer, List<VisitDetails>> getVisitsForPets(final List<Integer> petIds, final int ownerId) { - //TODO: expose batch interface in Visit Service - final ParameterizedTypeReference<List<VisitDetails>> responseType = new ParameterizedTypeReference<List<VisitDetails>>() { - }; - return petIds.parallelStream() - .flatMap(petId -> loadBalancedRestTemplate.exchange("http://visits-service/owners/{ownerId}/pets/{petId}/visits", HttpMethod.GET, null, - responseType, ownerId, petId).getBody().stream()) + public Map<Integer, List<VisitDetails>> getVisitsForPets(final List<Integer> petIds) { + UriComponentsBuilder builder = fromHttpUrl("http://visits-service/pets/visits") + .queryParam("petId", joinIds(petIds)); + + return loadBalancedRestTemplate.getForObject(builder.toUriString(), Visits.class) + .getItems() + .stream() .collect(groupingBy(VisitDetails::getPetId)); } + + private String joinIds(List<Integer> petIds) { + return petIds.stream().map(Object::toString).collect(joining(",")); + } } diff --git a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/boundary/web/ApiGatewayController.java b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/boundary/web/ApiGatewayController.java index 349390686d21c420c7b910bb4c92df08631d116f..9d9828c8ca80eed8598a4c66551f5e2def17da5c 100644 --- a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/boundary/web/ApiGatewayController.java +++ b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/boundary/web/ApiGatewayController.java @@ -15,19 +15,18 @@ */ package org.springframework.samples.petclinic.api.boundary.web; +import java.util.List; +import java.util.Map; +import java.util.Optional; import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.samples.petclinic.api.application.CustomersServiceClient; -import org.springframework.samples.petclinic.api.application.OwnerDetails; -import org.springframework.samples.petclinic.api.application.VisitDetails; +import org.springframework.samples.petclinic.api.dto.OwnerDetails; import org.springframework.samples.petclinic.api.application.VisitsServiceClient; +import org.springframework.samples.petclinic.api.dto.VisitDetails; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; -import java.util.List; -import java.util.Map; -import java.util.Optional; import static java.util.Collections.emptyList; @@ -45,7 +44,7 @@ public class ApiGatewayController { @GetMapping(value = "owners/{ownerId}") public OwnerDetails getOwnerDetails(final @PathVariable int ownerId) { final OwnerDetails owner = customersServiceClient.getOwner(ownerId); - supplyVisits(owner, visitsServiceClient.getVisitsForPets(owner.getPetIds(), ownerId)); + supplyVisits(owner, visitsServiceClient.getVisitsForPets(owner.getPetIds())); return owner; } diff --git a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/OwnerDetails.java b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/OwnerDetails.java similarity index 92% rename from spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/OwnerDetails.java rename to spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/OwnerDetails.java index ca2be1cdcca1f5691718441ee84092fafb9f31c0..5cea00a88f64294c2db99f81cdf596ecb5ccb3c8 100644 --- a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/OwnerDetails.java +++ b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/OwnerDetails.java @@ -13,11 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.samples.petclinic.api.application; +package org.springframework.samples.petclinic.api.dto; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; -import lombok.NoArgsConstructor; import java.util.ArrayList; import java.util.List; diff --git a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/PetDetails.java b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/PetDetails.java similarity index 90% rename from spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/PetDetails.java rename to spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/PetDetails.java index b2b40d0e4a4496e26a07cfe85d078c6c2c498c26..e9630d07dae2372b3a39abfb649f88e5cb2370fd 100644 --- a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/PetDetails.java +++ b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/PetDetails.java @@ -13,10 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.samples.petclinic.api.application; +package org.springframework.samples.petclinic.api.dto; import lombok.Data; -import lombok.NoArgsConstructor; import java.util.ArrayList; import java.util.List; diff --git a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/PetType.java b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/PetType.java similarity index 88% rename from spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/PetType.java rename to spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/PetType.java index bd284b558c608a7832ad31417d16b9a9721b04fd..63d2b2005318a08ed4740edd81742847b3dd1488 100644 --- a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/PetType.java +++ b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/PetType.java @@ -13,10 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.samples.petclinic.api.application; +package org.springframework.samples.petclinic.api.dto; import lombok.Data; -import lombok.NoArgsConstructor; /** * @author Maciej Szarlinski diff --git a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/VisitDetails.java b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/VisitDetails.java similarity index 86% rename from spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/VisitDetails.java rename to spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/VisitDetails.java index beefaa3fb8667ddee7733e7dd6f3e574316a359a..29b7c21927030b9c479b71fc81c1dbfda30656c9 100644 --- a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/VisitDetails.java +++ b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/VisitDetails.java @@ -13,15 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.samples.petclinic.api.application; +package org.springframework.samples.petclinic.api.dto; -import lombok.Data; -import lombok.NoArgsConstructor; +import lombok.Value; /** * @author Maciej Szarlinski */ -@Data +@Value public class VisitDetails { private int id; diff --git a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/Visits.java b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/Visits.java new file mode 100644 index 0000000000000000000000000000000000000000..58708bc91e4d2082cd16ea0a5200f65970c01c59 --- /dev/null +++ b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/Visits.java @@ -0,0 +1,27 @@ +/* + * Copyright 2002-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.samples.petclinic.api.dto; + +import java.util.List; +import lombok.Value; + +/** + * @author Maciej Szarlinski + */ +@Value +public class Visits { + private List<VisitDetails> items; +} diff --git a/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/model/Visit.java b/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/model/Visit.java index 807c2cd4e6dbfde03721fbfc0f9dfd1e9514e8db..f799b43a0366d029be3f9c66f54cee21abacbf72 100644 --- a/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/model/Visit.java +++ b/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/model/Visit.java @@ -15,8 +15,8 @@ */ package org.springframework.samples.petclinic.visits.model; +import com.fasterxml.jackson.annotation.JsonFormat; import java.util.Date; - import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; @@ -26,8 +26,9 @@ import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.TemporalType; import javax.validation.constraints.Size; - -import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.NoArgsConstructor; /** * Simple JavaBean domain object representing a visit. @@ -37,12 +38,16 @@ import com.fasterxml.jackson.annotation.JsonFormat; */ @Entity @Table(name = "visits") +@Builder(builderMethodName = "visit") +@NoArgsConstructor +@AllArgsConstructor public class Visit { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; + @Builder.Default @Column(name = "visit_date") @Temporal(TemporalType.TIMESTAMP) @JsonFormat(pattern = "yyyy-MM-dd") diff --git a/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/model/VisitRepository.java b/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/model/VisitRepository.java index 15796fff2690feabb865ea22d8f848590a396de4..b4f8689be8219aa164b47666928cb83772c9c1f9 100644 --- a/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/model/VisitRepository.java +++ b/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/model/VisitRepository.java @@ -33,4 +33,5 @@ public interface VisitRepository extends JpaRepository<Visit, Integer> { List<Visit> findByPetId(int petId); + List<Visit> findByPetIdIn(Iterable<Integer> petIds); } diff --git a/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/web/VisitResource.java b/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/web/VisitResource.java index 0d0199fba6d8a8b035c1fe65b117ccfa59587a7f..2ebab12e1b4fbc518d2d085e440d8c7161e4635f 100644 --- a/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/web/VisitResource.java +++ b/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/web/VisitResource.java @@ -15,16 +15,21 @@ */ package org.springframework.samples.petclinic.visits.web; +import java.util.List; +import javax.validation.Valid; import lombok.RequiredArgsConstructor; +import lombok.Value; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.samples.petclinic.visits.model.Visit; import org.springframework.samples.petclinic.visits.model.VisitRepository; -import org.springframework.web.bind.annotation.*; - -import javax.validation.Valid; -import java.util.List; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; /** * @author Juergen Hoeller @@ -36,13 +41,13 @@ import java.util.List; @RestController @RequiredArgsConstructor @Slf4j -public class VisitResource { +class VisitResource { private final VisitRepository visitRepository; @PostMapping("owners/*/pets/{petId}/visits") @ResponseStatus(HttpStatus.NO_CONTENT) - public void create( + void create( @Valid @RequestBody Visit visit, @PathVariable("petId") int petId) { @@ -52,7 +57,18 @@ public class VisitResource { } @GetMapping("owners/*/pets/{petId}/visits") - public List<Visit> visits(@PathVariable("petId") int petId) { + List<Visit> visits(@PathVariable("petId") int petId) { return visitRepository.findByPetId(petId); } + + @GetMapping("pets/visits") + Visits visitsMultiGet(@RequestParam("petId") List<Integer> petIds) { + final List<Visit> byPetIdIn = visitRepository.findByPetIdIn(petIds); + return new Visits(byPetIdIn); + } + + @Value + static class Visits { + private final List<Visit> items; + } } diff --git a/spring-petclinic-visits-service/src/test/java/org/springframework/samples/petclinic/visits/web/VisitResourceTest.java b/spring-petclinic-visits-service/src/test/java/org/springframework/samples/petclinic/visits/web/VisitResourceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..4724aa81ee3c43ad023e22a291213ee26c58219a --- /dev/null +++ b/spring-petclinic-visits-service/src/test/java/org/springframework/samples/petclinic/visits/web/VisitResourceTest.java @@ -0,0 +1,61 @@ +package org.springframework.samples.petclinic.visits.web; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.samples.petclinic.visits.model.VisitRepository; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; + + +import static java.util.Arrays.asList; +import static org.mockito.BDDMockito.given; +import static org.springframework.samples.petclinic.visits.model.Visit.visit; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@RunWith(SpringRunner.class) +@WebMvcTest(VisitResource.class) +@ActiveProfiles("test") +public class VisitResourceTest { + + @Autowired + MockMvc mvc; + + @MockBean + VisitRepository visitRepository; + + @Test + public void shouldFetchVisits() throws Exception { + given(visitRepository.findByPetIdIn(asList(111, 222))) + .willReturn( + asList( + visit() + .id(1) + .petId(111) + .build(), + visit() + .id(2) + .petId(222) + .build(), + visit() + .id(3) + .petId(222) + .build() + ) + ); + + mvc.perform(get("/pets/visits?petId=111,222")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.items[0].id").value(1)) + .andExpect(jsonPath("$.items[1].id").value(2)) + .andExpect(jsonPath("$.items[2].id").value(3)) + .andExpect(jsonPath("$.items[0].petId").value(111)) + .andExpect(jsonPath("$.items[1].petId").value(222)) + .andExpect(jsonPath("$.items[2].petId").value(222)); + } +}