Spring HATEOAS API - TheOpenCloudEngine/uEngine-cloud GitHub Wiki
HATEOAS๋ link๊ตฌ์กฐ๋ฅผ ๊ฐ์ ธ์, ์์ ๊ณผ ์ฐ๊ด๋ ๋ค๋ฅธ microservice๋ฅผ ์ฐ๊ฒฐํ๊ธฐ ์ํ์ฌ ํ์ํ๋ค.
์ด๋ฒ ํ์ด์ง์์๋ Spring MVC ๊ธฐ๋ฐ RPC ์์ ์ค๋ช ํ ์์ ๋ฅผ ๋ณด๊ฐํ์ฌ HATEOAS API๋ฅผ ๋ง๋๋ ๋ฐฉ๋ฒ์ ์์๋ณผ ๊ฒ์ด๋ค.
@RestController
public class SharedCalendarServiceImpl implements SharedCalendarService {
@Autowired
ScheduleRepository scheduleRepository;
@Override
public Resources<Resource> getSchedules(@PathVariable("instructorId") Long instructorId, @PathVariable("date") String date) {
Date realDate = convertToDate(date);
List<Schedule> schedules = scheduleRepository.findByInstructorId(instructorId);
List<Resource> list = new ArrayList<Resource>();
for(Schedule schedule : schedules) {
if(DateUtils.isSameDay(schedule.getDate(), realDate))
list.add(new Resource<Schedule>(schedule));
}
Resources<Resource> halResources = new Resources<Resource>(list);
//return new ScheduleResource(schedules);
return halResources;
}
}
์ฐ์ ๋์ ๋๋ ์ฐจ์ด์ ์ ResourceSupport
๋ก return ์ ์ํ๊ณ ์กฐ๊ธ ๋ detail ํ๊ฒ,
ArrayList๋ก ๋ง๋ค์ด์ง Resource ๊ฐ์ฒด๋ฅผ Resouces ๋ก ๋ณํ์ ํ์ฌ return์ ์ํจ๋ค.
- Resource : ์ ์ผํ ์๋ณ์/์ฃผ์(๋งํฌ) ํํ๋ก ์์ ์ ํํ
- Resources : Resource ์ Collection ํํ
- ResourceSupport : Resource ๋ฅผ ์ถ์์ ์ผ๋ก ์ ์ (Resource extends ResourceSupport)
Resources๋ฅผ ์ฌ์ฉํด์ผ์ง ์ผ๋ฐ HATEOAS API์ฒ๋ผ _embedded์ ๋ด๊ฒจ์ง๋ค.
Resources๋ฅผ ์ด์ฉํ์ฌ _embedded ํํ๋ก ๋ฆฌํดํ์ง ์์ ๊ฒฝ์ฐ, ๊ฐ์ฒด๊ฐ Jsonํํ๋ก ๋์ด์ค๊ฑฐ๋, ๋ค๋ฅธ ์ ๋ณด ์์ด ๋งํฌ๋ง ๋ฆฌํดํ ์ ์๋ค.
_embedded๋ HAL(Hypertext Application Language) format์ผ๋ก์ front-end์์ Hateoas ์์ค์ผ๋ก ์ ๋ณด๊ฐ ๋ด๊ฒจ์๋ค๊ณ ์ธ์งํ์ฌ ์ฌ์ฉํ๊ฒ ๋๋ค.
์ฌ๊ธฐ๊น์งํ๊ณ , ์ค์ _embedded ๋ฐฉ์์ผ๋ก return์ ํ๋์ง ํ์ธ์ ํด๋ณธ๋ค.
$ git clone https://github.com/uengine-oss/msa-tutorial-class-management-msa.git
$ cd msa-tutorial-class-management-msa/calendar
## port=8085 ์๋ฒ์์
$ mvn spring-boot:run -Dserver.port=8085
## ๊ฐ์ฌ ์ถ๊ฐ
$ http localhost:8085/schedules instructorId=1 date="2018-3-14"
## getSchedules ๋ฉ์๋ remote call
$ http localhost:8085/calendar/1/2018-3-14
{
"_embedded": {
"schedules": [
{
"date": 1520985600000,
"id": 1,
"instructorId": 1,
"title": null
}
]
}
}
์๋ฒ ์์์ error๊ฐ ๋ฐ์ํ ์ ์์ง๋ง, eureka๋ฅผ ์๋์ด ๋ถ๋ถ์ด๋๊น ํ์ฌ ์์ ์์๋ ๋ฌด์ํด๋ ์๊ด์๋ค.
_embedded ํํ๋ก ๋ฐ์ดํฐ๋ ๋์ค์ง๋ง, ๋ค๋ฅธ ์๋น์ค์์ get์ด๋ delete๋ฅผ ํ๊ธฐ ์ํ "_links" ์ ๋ณด๊ฐ ์๋ค.
Link ์ ๋ณด๋ฅผ ์ฃผ๊ธฐ ์ํ์ฌ ์์ ์ฝ๋๋ฅผ ์์ ํด์ผ ํ๋ค.
for(Schedule schedule : schedules) {
if(DateUtils.isSameDay(schedule.getDate(), realDate))
// list.add(new Resource<Schedule>(schedule));
Resource<Schedule> resource = new Resource<Schedule>(schedule);
resource.add(new Link("http://localhost:8085/calendar/" + instructorId + "/" + date, "_self"));
list.add(resource);
}
$ http localhost:8085/schedules instructorId=1 date="2018-3-14"
## getSchedules ๋ฉ์๋ remote call
$ http localhost:8085/calendar/1/2018-3-14
{
"_embedded": {
"schedules": [
{
"_links": {
"_self": {
"href": "http://localhost:8085/calendar/1/2018-03-14"
}
},
"date": 1520985600000,
"id": 1,
"instructorId": 1,
"title": null
}
]
}
}
_links์ ๋ณด๊ฐ ์๊ธด ๊ฒ์ ํ์ธ ํ ์ ์๋ค.
์ด๊ฒ์ self์ด๊ธฐ ๋๋ฌธ์ ๊ฐ๋จํ๊ฒ ํ์๊ณ , ์ค์ ๋ก microservice๋ฅผ ์ฎ๋ ์์
์ ํ๋ ค๋ฉด
์ ๋ ๊ฒ uri ์ ๋ณด๋ฅผ ๋ฐ๋ก ์
๋ ฅํ๋ฉด ์๋๋ค.
resource.add(linkTo(methodOn(SharedCalendarService.class).getSchedules(instructorId,date)).withSelfRel());
// resource.add(new Link("http://localhost:8085/calendar/" + instructorId + "/" + date, "_self"));
ํด์์ ํ์๋ฉด resource์ link๋ฅผ addํ๋๋ฐ ๋ด ์์ ์ JAX-RS ์ ๋ณด๋ฅผ ํธ์ถํ ์ ์๋ URL์ ์์ฑํ์ฌ self๋ก ์ฐ๊ฒฐ์ ํ์๋ค. ์ด์ new Link ์ ๋ค๋ฅธ์ ์ RequestMapping์ path์ ๋ณด๊ฐ์ spec์ด ๋ณ๊ฒฝ๋์ด๋, ๋ด๋ถ ๊ตฌํ์ ๋ณ๊ฒฝ์์ด ์ฌ์ฉ ๊ฐ๋ฅํ๋ค.
์ด์ self ์ ๋ณด๊ฐ ์๋, ์ฐ๊ณ๋๋ request ์ ๋ณด๊ฐ ์์๊ฒฝ์ฐ์๋ ์๋์ ๊ฐ์ด ์ค์ ์ด ๊ฐ๋ฅํ๋ค.
์ฐ์ ์ค์ผ์ฅด์ delay์ํค๋ ๋ฉ์๋๋ฅผ ํ๋ ๋ง๋ ๋ค.
@RequestMapping(method = RequestMethod.PUT, path="/calendar/{instructorId}/{date}/delay")
public Resources<Resource> delaySchedules(
@PathVariable("instructorId") Long instructorId, @PathVariable("date") String date,
@RequestParam("delayDays") int days
) {
Resources<Resource> schedules = getSchedules(instructorId, date);
// TODO day ์ฆ๊ฐ
return schedules;
}
๊ทธ๋ฆฌ๊ณ resource์ link๋ฅผ self๊ฐ ์๋ relation์ ๋ณด๋ฅผ ์๋์ ๊ฐ์ด ์ถ๊ฐํ์ฌ ์ค๋ค.
resource.add(linkTo(methodOn(SharedCalendarServiceImpl.class).delaySchedules(instructorId,date,0)).withRel("delay"));
$ http localhost:8085/schedules instructorId=1 date="2018-3-14"
## getSchedules ๋ฉ์๋ remote call
$ http localhost:8085/calendar/1/2018-3-14
{
"_embedded": {
"schedules": [
{
"_links": {
"delay": {
"href": "http://localhost:8085/calendar/1/2018-03-14/delay?delayDays=0"
},
"self": {
"href": "http://localhost:8085/calendar/1/2018-03-14"
}
},
"date": 1520985600000,
"id": 1,
"instructorId": 1,
"title": null
}
]
}
}
delay๋ผ๋ link๊ฐ ์์ฑ๋ ๊ฒ์ ํ์ธ ํ ์ ์๋ค.