플레이어가 여러 팀에있을 수있는 두 개의 엔티티 ( Player 및 Team) 가 있다고 가정하십시오 . 내 데이터 모델에는 각 엔터티에 대한 테이블과 관계를 유지하기위한 조인 테이블이 있습니다. Hibernate는 이것을 잘 처리하지만 RESTful API에서 어떻게이 관계를 노출시킬 수 있습니까?
몇 가지 방법을 생각할 수 있습니다. 먼저 각 엔터티에 다른 엔터티의 목록이 포함되어있을 수 있으므로 Player 객체에는 소속 된 팀 목록이 있고 각 Team 객체에는 소속 된 플레이어 목록이 있습니다. 따라서 플레이어를 팀에 추가하려면 플레이어의 표현을 요청의 페이로드로 적절한 객체를 사용하여 POST /player
또는 POST /team
와 같은 엔드 포인트에 POST 하면됩니다. 이것은 나에게 가장 “RESTful”인 것처럼 보이지만 조금 이상하다고 느낍니다.
/api/team/0:
{
name: 'Boston Celtics',
logo: '/img/Celtics.png',
players: [
'/api/player/20',
'/api/player/5',
'/api/player/34'
]
}
/api/player/20:
{
pk: 20,
name: 'Ray Allen',
birth: '1975-07-20T02:00:00Z',
team: '/api/team/0'
}
내가 이것을 생각할 수있는 다른 방법은 관계를 그 자체로 리소스로 노출시키는 것입니다. 따라서 주어진 팀의 모든 플레이어 목록을 보려면 GET /playerteam/team/{id}
또는 이와 비슷한 작업을 수행하고 PlayerTeam 엔티티 목록을 다시 가져올 수 있습니다. 팀에 플레이어를 추가하려면 /playerteam
적절하게 빌드 된 PlayerTeam 엔티티를 페이로드로 POST 하십시오.
/api/team/0:
{
name: 'Boston Celtics',
logo: '/img/Celtics.png'
}
/api/player/20:
{
pk: 20,
name: 'Ray Allen',
birth: '1975-07-20T02:00:00Z',
team: '/api/team/0'
}
/api/player/team/0/:
[
'/api/player/20',
'/api/player/5',
'/api/player/34'
]
가장 좋은 방법은 무엇입니까?
답변
RESTful 인터페이스에서 해당 관계를 링크로 인코딩하여 자원 간의 관계를 설명하는 문서를 리턴 할 수 있습니다. 따라서 팀에는 팀의 /team/{id}/players
플레이어에 대한 링크 목록 인 문서 리소스 ( )가 있다고 말하고 /player/{id}
플레이어는 문서 리소스 (/player/{id}/teams
)는 플레이어가 속한 팀에 대한 링크 목록입니다. 멋지고 대칭 적입니다. 당신이 쉽게 일을 할 수 있다면 당신은 그 목록에 쉽게 맵 오퍼레이션을 할 수 있고, 관계에 자신의 ID를 줄 수도 있습니다 (아마도 팀 우선 또는 플레이어 우선 관계에 대해 생각하고 있는지에 따라 두 개의 ID를 가질 것입니다) . 유일한 까다로운 점은 한쪽 끝에서 관계를 삭제하면 다른 쪽 끝에서도 관계를 삭제하는 것을 기억해야하지만 기본 데이터 모델을 사용하여 엄격하게 처리 한 다음 REST 인터페이스를 그 모델이 더 쉬워 질 것입니다.
관계 ID는 팀 및 플레이어에 사용하는 ID 유형에 관계없이 UUID 또는 동일하고 길고 임의의 항목을 기반으로해야합니다. 즉 (작은 정수는 않습니다 당신이 충돌에 대한 걱정없이 관계의 각 끝의 ID 구성 요소와 동일한 UUID를 사용하게됩니다 하지 그 장점이 있습니다). 이러한 멤버십 관계에 양방향 방식으로 선수와 팀을 관련 시킨다는 사실 이외의 다른 속성이있는 경우, 선수와 팀 모두와 독립적 인 고유 한 정체성을 가져야합니다. 플레이어»팀보기 ( /player/{playerID}/teams/{teamID}
) 의 GET 은 양방향보기 ( /memberships/{uuid}
) 로 HTTP 리디렉션을 수행 할 수 있습니다 .
XLink xlink:href
속성을 사용하여 (물론 XML을 생성하는 경우) 반환하는 XML 문서에 링크를 작성하는 것이 좋습니다 .
답변
별도의 /memberships/
리소스 세트를 만드십시오 .
- REST는 다른 것이 없다면 진화 가능한 시스템을 만드는 것입니다. 지금이 순간, 당신은 단지 주어진 플레이어가 주어진 팀에 있는지 신경 수 있지만, 미래의 어떤 시점에서, 당신은 것입니다 더 많은 데이터와 그 관계에 주석 할 : 그들은 팀에 있었던 기간, 그들을 참조 그 팀에, 코치가 누구인지, 그 팀에있을 때 등
- REST는 효율성을위한 캐싱에 따라 달라지며, 캐시 원 자성 및 무효화를 고려해야합니다.
/teams/3/players/
해당 목록에 새 엔티티를 POST하면 무효화되지만 대체 URL/players/5/teams/
을 캐시 된 상태로 유지 하지 않으려 고합니다 . 예, 다른 캐시에는 연령이 다른 각 목록의 사본이 있으며 그에 대해 할 수있는 일은 많지 않지만 무효화해야하는 엔티티 수를 제한하여 업데이트를 POST하는 사용자의 혼동을 최소한 최소화 할 수 있습니다. 그들의 클라이언트의 로컬 캐시에 오직 하나 의/memberships/98745
(에 “대체 지표”의 Helland의 토론 참조 분산 트랜잭션을 넘어 생활 에 대한 자세한 설명을 위해). - 당신은 단순히 선택하여 2 점 위를 구현할 수
/players/5/teams
또는/teams/3/players
(하지만 모두). 전자를 가정 해 봅시다. 그러나 어느 시점에서 현재 멤버십/players/5/teams/
목록 을 예약 하고 어딘가 에서 과거 멤버십 을 참조 할 수 있습니다 . 확인 에 대한 하이퍼 링크의 목록 자원을, 그리고 당신은 추가 할 수 있습니다 당신이 좋아하는 경우 개별 회원 리소스에 대한 모든 사람의 북마크를 중단하지 않고. 이것은 일반적인 개념입니다. 특정 사례에 더 적합한 다른 유사한 미래를 상상할 수 있습니다./players/5/memberships/
/memberships/{id}/
/players/5/past_memberships/
답변
하위 리소스와의 관계를 매핑하면 일반적인 디자인 / 탐색은 다음과 같습니다.
# team resource
/teams/{teamId}
# players resource
/players/{playerId}
# teams/players subresource
/teams/{teamId}/players/{playerId}
편안한 용어로 SQL을 생각하지 않고 콜렉션, 하위 콜렉션 및 순회에 더 많이 참여하는 데 도움이됩니다.
몇 가지 예 :
# getting player 3 who is on team 1
# or simply checking whether player 3 is on that team (200 vs. 404)
GET /teams/1/players/3
# getting player 3 who is also on team 3
GET /teams/3/players/3
# adding player 3 also to team 2
PUT /teams/2/players/3
# getting all teams of player 3
GET /players/3/teams
# withdraw player 3 from team 1 (appeared drunk before match)
DELETE /teams/1/players/3
# team 1 found a replacement, who is not registered in league yet
POST /players
# from payload you get back the id, now place it officially to team 1
PUT /teams/1/players/44
보시다시피 플레이어를 팀에 배치하는 데 POST를 사용하지 않지만 플레이어와 팀의 n : n 관계를 더 잘 처리하는 PUT을 사용합니다.
답변
기존 답변 에는 일관성 및 dem 등원의 역할이 설명 되어 있지 않습니다. 이는 UUIDs
ID PUT
대신 / 랜덤 번호에 대한 권장 사항을 동기 부여합니다 POST
.
” 팀에 새 선수 추가 “와 같은 간단한 시나리오가있는 경우 일관성 문제가 발생합니다.
플레이어가 존재하지 않기 때문에 다음을 수행해야합니다.
POST /players { "Name": "Murray" } //=> 302 /players/5
POST /teams/1/players/5
그러나 POST
to 다음에 클라이언트 작업이 실패 /players
하면 팀에 속하지 않은 플레이어를 만들었습니다.
POST /players { "Name": "Murray" } //=> 302 /players/5
// *client failure*
// *client retries naively*
POST /players { "Name": "Murray" } //=> 302 /players/6
POST /teams/1/players/6
이제에 고아 중복 플레이어가 /players/5
있습니다.
이 문제를 해결하기 위해 일부 자연 키 (예 :)와 일치하는 고아 플레이어를 확인하는 사용자 지정 복구 코드를 작성할 수 있습니다 Name
. 이것은 테스트가 필요한 사용자 정의 코드이며 더 많은 비용과 시간이 소요됩니다.
사용자 지정 복구 코드가 필요하지 않도록 PUT
대신 대신 구현할 수 있습니다 POST
.
로부터 RFC :
의 의도
PUT
는 dem 등이다
조작이 dem 등원 (idempotent)이 되려면 서버에서 생성 된 id 시퀀스와 같은 외부 데이터를 제외해야합니다. 이것은 사람들이 모두 추천하는 이유 PUT
와 UUID
대한의 Id
함께들.
이를 통해 결과 /players
PUT
와 /memberships
PUT
결과를 모두 다시 실행할 수 있습니다 .
PUT /players/23lkrjrqwlej { "Name": "Murray" } //=> 200 OK
// *client failure*
// *client YOLOs*
PUT /players/23lkrjrqwlej { "Name": "Murray" } //=> 200 OK
PUT /teams/1/players/23lkrjrqwlej
모든 것이 정상이며 부분 오류에 대해 재 시도하는 것 이상을 수행 할 필요가 없습니다.
이것은 기존 답변에 대한 추가 사항이지만 ReST가 얼마나 유연하고 신뢰할 수 있는지에 대한 더 큰 그림의 맥락에서 설명하기를 바랍니다.
답변
나의 선호하는 솔루션은 세 가지 리소스를 생성하는 것입니다 Players
, Teams
하고 TeamsPlayers
.
따라서 팀의 모든 플레이어를 얻으려면 Teams
리소스 로 이동 하여을 호출하여 모든 플레이어를 얻으십시오 GET /Teams/{teamId}/Players
.
반면, 플레이어가 한 팀을 모두 확보하려면의 Teams
리소스를 얻으 십시오 Players
. 전화하십시오 GET /Players/{playerId}/Teams
.
다 대 다 관계 전화를 받으려면 GET /Players/{playerId}/TeamsPlayers
또는 GET /Teams/{teamId}/TeamsPlayers
.
이 솔루션 에서을 호출하면을 호출 할 때 얻는 것과 정확히 동일한 리소스 GET /Players/{playerId}/Teams
배열이 생성 Teams
됩니다 GET /Teams/{teamId}
. 그 반대의 경우도 마찬가지 입니다. Players
call시 여러 리소스 를 얻습니다 GET /Teams/{teamId}/Players
.
두 호출에서 관계에 대한 정보가 리턴되지 않습니다. 예를 들어, contractStartDate
리턴 된 자원에 관계에 대한 정보가없고 자체 자원에 대한 정보 만 있기 때문에 no 가 리턴됩니다.
nn 관계를 처리하려면 GET /Players/{playerId}/TeamsPlayers
또는을 호출하십시오 GET /Teams/{teamId}/TeamsPlayers
. 이 호출은 정확히 리소스를 반환합니다 TeamsPlayers
.
이 TeamsPlayers
자원이있다 id
, playerId
, teamId
속성뿐만 아니라 일부 다른 관계를 설명합니다. 또한이를 처리하는 데 필요한 방법이 있습니다. 관계 자원을 리턴, 포함, 갱신 및 제거 할 GET, POST, PUT, DELETE 등.
TeamsPlayers
자원 구현 일부 쿼리는 원하는 GET /TeamsPlayers?player={playerId}
모든 반환 TeamsPlayers
에 의해 확인 된 플레이어는 관계 {playerId}
가 있습니다. 같은 생각 에 따라 팀 에서 플레이 한 GET /TeamsPlayers?team={teamId}
모든 것을 반환하십시오 . 어느 호출 에서나 리소스 가 반환됩니다. 관계와 관련된 모든 데이터가 반환됩니다.TeamsPlayers
{teamId}
GET
TeamsPlayers
GET /Players/{playerId}/Teams
(또는 GET /Teams/{teamId}/Players
)을 호출 할 때 리소스 Players
(또는 Teams
) TeamsPlayers
는 쿼리 필터를 사용하여 관련 팀 (또는 플레이어)을 반환하기 위해 호출 합니다.
GET /Players/{playerId}/Teams
다음과 같이 작동합니다.
- 플레이어 가 id = playerId 인 모든 TeamsPlayer 를 찾으십시오 . ( )
GET /TeamsPlayers?player={playerId}
- 반환 된 TeamsPlayers 루프
- TeamsPlayers 에서 얻은 teamId 를 사용하여 리턴 된 데이터를 호출 하고 저장하십시오.
GET /Teams/{teamId}
- 루프가 끝난 후. 루프에 들어간 모든 팀을 반환하십시오.
동일한 알고리즘을 사용하여에 전화 할 때 팀에서 모든 플레이어를 가져 GET /Teams/{teamId}/Players
오지만 팀과 플레이어를 교환 할 수 있습니다.
내 리소스는 다음과 같습니다.
/api/Teams/1:
{
id: 1
name: 'Vasco da Gama',
logo: '/img/Vascao.png',
}
/api/Players/10:
{
id: 10,
name: 'Roberto Dinamite',
birth: '1954-04-13T00:00:00Z',
}
/api/TeamsPlayers/100
{
id: 100,
playerId: 10,
teamId: 1,
contractStartDate: '1971-11-25T00:00:00Z',
}
이 솔루션은 REST 자원에만 의존합니다. 플레이어, 팀 또는 관계로부터 데이터를 가져 오는 데 약간의 추가 호출이 필요할 수 있지만 모든 HTTP 메소드는 쉽게 구현됩니다. POST, PUT, DELETE는 간단하고 간단합니다.
관계가 작성, 업데이트 또는 삭제 될 때마다 Players
및 Teams
자원이 모두 자동으로 업데이트됩니다.
답변
이 질문에 허용되는 것으로 표시되어 있지만 이전에 제기 된 문제를 해결하는 방법은 다음과 같습니다.
PUT에 대해 말해 봅시다
PUT /membership/{collection}/{instance}/{collection}/{instance}/
예를 들어, 다음은 단일 리소스에서 수행되므로 동기화 할 필요없이 동일한 결과를 가져옵니다.
PUT /membership/teams/team1/players/player1/
PUT /membership/players/player1/teams/team1/
이제 한 팀의 여러 멤버십을 업데이트하려는 경우 다음과 같이 수행 할 수 있습니다 (적절한 검증).
PUT /membership/teams/team1/
{
membership: [
{
teamId: "team1"
playerId: "player1"
},
{
teamId: "team1"
playerId: "player2"
},
...
]
}
답변
- / players (마스터 리소스)
- / teams / {id} / players (관계 리소스이므로 1과 다른 반응을 보입니다)
- / 멤버쉽 (관계는 있지만 의미 적으로 복잡합니다)
- / players / memberships (관계는 있지만 의미 상 복잡합니다)
나는 2를 선호한다