개발자라면 누구나 코드에서 과거의 유산을 발견하게 된다. 더 이상 사용되지는 않지만 미처 지우지 못했거나 지울 필요를 느끼지 못해서 방치된 코드일 수도 있고, 레퍼런스를 위해 일부러 지우지 않은 코드일 수도 있다. 보통 이런 코드는 죽은 코드이기 때문에 애플리케이션에 별다른 영향을 미치지 못한다. 정말로 사용되고 있지 않다면 말이다.
새 기능을 위해 BE를 개발하던 중, 과거에 이 기능을 위해 정의된 GraphQL 필드가 있는 것을 발견했다. 이 기능을 팔로우 기능이라고 해보자. 그렇다면 유저 타입과 팔로우 타입을 아래처럼 정의할 수 있을 것이다.
type User {
id: ID!
name: String!
createdDateTime: DateTime!
...
follow: UserFollow!
}
type UserFollow {
id: ID!
count: Int!
isFollowing: Boolean!
follower(first: Int, limit: Int): UserConnection!
followee(first: Int, limit: Int): UserConnection!
}
여기까지는 별 문제가 없어보인다. 팔로우 기능은 아직 구현되지 않았고, User.follow
필드는 mock만 반환하고 있으며, 해당 필드를 사용하는 클라이언트 코드는 없음에도 불구하고 이대로 배포되었다는 사실을 알기 전까지는 말이다.
클라이언트 입장에서, 기능이 구현되지도 않은 상태의 User.follow
필드를 아직 필요하지는 않지만 User
를 쿼리할 때 미리 가져오도록 결정했다고 하자.
query getUser ($id: ID!) {
user(id: $id) {
id
name
follow {
id
count
isFollowing # isViewerFollowing으로 변경
follower(first: 5, limit: 3) {
edges {
...
}
pageInfo {
...
}
}
}
}
}
만약 서버에서 팔로우의 하위 필드를 바꾸고자 한다고, 가령 isFollowing
필드의 이름을 isViewerFollowing
필드로 바꾸려 한다고 해보자. 웹 환경에서는 큰 문제가 되지 않는다. 웹 클라이언트 코드에서 팔로우 필드를 수정한 후 배포하면, 브라우저에서 변경된 자바스크립트 번들을 가져와서 실행할 것이기 때문이다. 그런데 앱 환경은 이야기가 다르다. isViewerFollowing
필드로 수정한 최신 빌드의 앱은 isViewerFollowing
필드로 요청을 보낼 것이고 서버는 이를 문제 없이 처리할 것이다. 하지만 구 버전의 앱을 가지고 있는 기기들은 모두 isFollowing
필드를 달라고 요청할 것이고, 서버는 "엥 그런 필드 없는데요"라고 에러를 띄울 것이다.
팔로우를 이용자에게뿐만 아니라 어떤 주제에 대해서도 가능하게 하는 새 기능을 추가한다고 해보자. 그리고 이와 같은 기능의 확장이 추후에도 일어날 수 있어서, 일반화된 팔로우 기능을 구현하는 것으로 결정했다고 하자. 이 때 모종의 이유로 isFollowing
을 isViewerFollowing
으로, follower
와 followee
의 파라미터를 (first: Int, after: Id)
로 바꿔야 한다고 하자. 그러면 타입은 아래와 같이 정의할 수 있을 것이다.
type User {
id: ID!
name: String!
createdDateTime: DateTime!
...
follow: Follow!
}
type Topic {
id: ID!
name: String!
...
follow: Follow!
}
type Follow {
id: ID!
count: Int!
isViewerFollowing: Boolean!
follower(first: Int, after: Id): UserConnection!
followee(first: Int, after: Id): UserConnection!
}
이런 상황이라면 구 버전의 앱에서는 위에서 말한 것처럼 바뀐 GraphQL 타입 때문에 에러가 날 것이다. 사용자의 머리에 총을 들이밀고 당장 앱을 업데이트하라고 하지 않는 이상 이는 좋은 상황이 아니다.
여기서 새 요구에 부응하고 구 버전의 앱의 호환성도 보장해주기 위해서 선택한 방법은, 기존 필드를 유지하되 deprecated로 마킹하고 새 필드도 같이 내려주는 것이었다 (followerList
, followeeList
는 필드 이름 충돌로 인한 개명).
type Follow {
id: ID!
count: Int!
isFollowing: Boolean! @deprecated(reason: "Use isViewerFollowing instead.")
isViewerFollowing: Boolean!
follower(first: Int, limit: Int): UserConnection! @deprecated(reason: "Use followerList instead.")
followee(first: Int, limit: Int): UserConnection! @deprecated(reason: "Use followeeList instead.")
followerList(first: Int, after: Id): UserConnection!
followeeList(first: Int, after: Id): UserConnection!
}
이런 식으로 구현하면 구 버전 앱에도 대응할 수 있고, 새 요구에도 맞춰줄 수 있다. deprecated된 필드들은 추후 구 버전 앱의 사용자들이 충분히 줄었을 때 빼버리면 된다.
서버는 아직 구현하지 않은 내용은 내려주지 말고 클라이언트는 자신이 확실히 쓸 내용만 서버에서 가져오도록 하여 레거시를 줄이고 효율을 높임으로써 프로젝트에 이바지하고 국부를 증진시켜 부국강병을 이룩할 수 있도록 해야겠다!