요깨비's LAB

[Spring Boot, Back-End] 5. 가게 상세 구현 본문

웹 개발/스프링 부트 프로젝트(레스토랑 예약)

[Spring Boot, Back-End] 5. 가게 상세 구현

요깨비 2020. 1. 7. 15:52

1. 가게 상세

  • GET방식 /restaurants/{id}
  • {
      "id":2019,
      "name":"식당",
      "address":"골목"
    }
    형태의 JSON값 반환

2. Code 구현

I. 컨트롤러 단 구현

1. RestaurantControllerTest 작성

@RunWith(SpringRunner.class)
@WebMvcTest(RestaurantController.class) // Restaurant 컨트롤러를 테스트하겠다는 어노테이션
public class RestaurantControllerTest {
	@Autowired
	private MockMvc mvc;

	// 이전 내용
	@Test
	public void list() throws Exception {
		...
	}
	
	@Test
	public void detail() throws Exception {
		mvc.perform(get("/restaurants/1004"))
			.andExpect(status().isOk())
			.andExpect(content().string(containsString("\"id\":1004")))
			.andExpect(content().string(containsString("\"name\":\"Mcdonald\"")))
			.andExpect(content().string(containsString("\"address\":\"Seoul\"")));
	}
}

자, 당연히 detail을 위한 컨트롤러가 없으니까 에러가 나겠죠? 이제 실제 컨트롤러 단을 작성할게요.

2. RestaurantController 작성

@RestController
public class RestaurantController {
	
    // 이전 챕터 내용
	@GetMapping("/restaurants")
	public List<Restaurant> list() {
    	List<Restaurant> restaurants = new ArrayList<>();
        
		restaurants.add(new Restaurant(1004L,"Mcdonald","Seoul"));
		restaurants.add(new Restaurant(2020L,"Cyber Food","Seoul"));
		
		return restaurants;
	}
	
	@GetMapping("/restaurants/{id}")
	public Restaurant detail(@PathVariable("id") Long id) {
		List<Restaurant> restaurants = new ArrayList<>();
		
		restaurants.add(new Restaurant(1004L,"Mcdonald","Seoul"));
		restaurants.add(new Restaurant(2020L,"Cyber Food","Seoul"));
		
        // 모던 자바의 기능인 Stream API 사용
		Restaurant restaurant = restaurants.stream()
				.filter(r -> r.getId().equals(id))
				.findFirst()
				.orElse(null);
		
		return restaurant;
	}
}

이제 테스트 코드가 정상 작동하게 됩니다. 그런데, detail과 list 코드를 보면

List<Restaurant> restaurants = new ArrayList<>();
        
restaurants.add(new Restaurant(1004L,"Mcdonald","Seoul"));
restaurants.add(new Restaurant(2020L,"Cyber Food","Seoul"));

이 코드가 중복되어 사용이 되고 있습니다. 이 부분을 하나로 묶어서 컨트롤러와 상관없는 곳에 두어서 유지 보수하기 좋게
리펙토링을 해야겠습니다. 해당 데이터들을 어딘가에 저장해 두고 이것들을 필요할때마다 불러오면 좋겠죠? 이제부터 
Repository 영역을 개발하겠습니다.

II. Repository 단 구현

1. Repository 클래스 구현(RestaurantRepository)

데이터를 저장하고 비지니스 로직 처리 기능을 구현할 Repository 클래스를 구현하겠습니다.

public class RestaurantRepository {
	// 현재는 리스트를 이용해서 기능을 구현했지만 데이터베이스를 활용하여 효과적으로 관리할 것.
	private List<Restaurant> restaurants = new ArrayList<>();

	public RestaurantRepository() {
		restaurants.add(new Restaurant(1004L, "Mcdonald", "Seoul"));
		restaurants.add(new Restaurant(2020L, "Cyber Food", "Seoul"));
	}

	public List<Restaurant> findAll() {
		return restaurants;
	}

	public Restaurant findById(Long id) {
		return restaurants.stream().filter(r -> r.getId().equals(id)).findFirst().orElse(null);
	}
}

구현 후에 Controller에 중복되어 존재하던 리스트 관련 코드를 삭제해줍니다.

List<Restaurant> restaurants = new ArrayList<>();
        
restaurants.add(new Restaurant(1004L,"Mcdonald","Seoul"));
restaurants.add(new Restaurant(2020L,"Cyber Food","Seoul"));

그 후에 아래의 컨트롤러 코드를 보겠습니다. 자세한 설명은 코드와 함께 보아야 이해가 쉬우므로, 주석으로 대체하였습니다.

@RestController
public class RestaurantController {
	private RestaurantRepository repository = new RestaurantRepository();
    
    	@GetMapping("/restaurants/{id}")
		public Restaurant detail(@PathVariable("id") Long id) {
//		레스토랑들을 가져와서 id값을 이용해서 특정 레스토랑을 찾고 있는데 이것은 컨트롤러의 역할이 아니다.
//		Repository의 findById()라는 메소드로 책임을 위임
//		Restaurant restaurant = restaurants.stream().filter(r -> r.getId().equals(id)).findFirst().orElse(null);
		Restaurant restaurant = repository.findById(id);

		return restaurant;
	}
}


코드 구현을 완료 후에 테스트를 실행하면 정상적으로 작동하는 것을 확인할 수 있습니다.

이럿듯, 중복된 코드를 하나의 통합 코드로 따로 분리하여 코드 수정 해야할 때 하나만 수정할 수 있도록 바꾸고, 가독성을
높여 유지 보수성을 높이고, 사용자와 내부에 있는 비즈니스 로직/도메인 모델들 사이의 징검다리 역할만 수행하도록
개발하고, 실제 로직을 수행하는 코드는 비즈니스 로직/도메인 모델에 분리함으로써 책임 분산이 확실하고, 확장성 있는 
코드를 개발할 수 있습니다. 현재 Repository는 리스트를 이용하여 기능을 구현하였지만, 추후에 데이터베이스에 연동하여
좀 더 효율적이고 뛰어난 기능으로 바꾸도록 하겠습니다.

Comments