-
Notifications
You must be signed in to change notification settings - Fork 0
[Feat] UserDefaults 정보 저장을 위한 UserSession 기능 구현 #109
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
JinUng41
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
고생하셨습니다!
코멘트에도 적어두었지만, 로그인한 유저 정보를 다루기 위한 UserSession 모델을 정의하신 점이 매우 인상적이었습니다.
특히 네이밍 부분에서 말이죠. 앞으로도 이런 네이밍을 지어주실 와블의 이유진님을 기대하고 있겠습니다.
유진님께서는 해당 객체를 Repository에서 활용할 Infra 수준의 객체로 인식하시어, 네이밍에 Storage 또는 Wrapper를 사용하신 것으로 보입니다. 그러나 저는 UserSession을 UserDefaults에 저장하는 객체로 정의하는 것이 Repository의 역할에 더 부합한다고 판단합니다. 즉, UserDefaults라는 저장 방식을 감추고 추상화한 객체는 단순한 Wrapper가 아니라 요구사항에 맞춘 Repository로 보는 것이 적절하다고 생각했어요.
-
첫 번째 이유는, UserSession Storage/Wrapper는 ‘로그인한 유저 정보’라는 특정 정보를 저장하고 관리한다는 점에서, Infra 수준의 범용 객체보다는 도메인 요구사항에 맞춘 Repository 수준의 객체가 적절하다는 것입니다.
-
두 번째 이유는, KeychainWrapper와 달리 UserSession Storage/Wrapper는 모든 데이터를 저장할 수 있는 형태가 아니기 때문입니다. KeychainWrapper는 모든 데이터 형식을 Data로 변환하여 저장할 수 있으며,
save,load,delete와 같은 통합 인터페이스를 제공하는 반면, UserSession Storage/Wrapper는 그러한 범용성을 갖추지 못합니다. -
마지막으로, Storage/Wrapper를 Repository 내부에서 사용하면 Repository가 제공해야 하는 정보 외의 데이터를 다루게 될 가능성이 있습니다. 오히려 Storage/Wrapper를 Repository 수준으로 끌어올리고, UseCase에서 UserSessionRepository와 다른 Repository를 함께 활용함으로써 해당 문제를 효과적으로 해소할 수 있다고 봅니다. UseCase가 단일 Repository만 이용해야 한다는 제약은 없으므로, 이 경우 UserSession이 도메인 모델로 정의되어야 하는 점에 대해 보다 심도 있게 검토할 필요가 있습니다.
그래서 결론적으로는 빠른 시일 내에 이와 관련해서 직접 토의를 해보면 좋을 것 같아요~
읽어보시고 유진님의 의견도 남겨주세요~ 화이팅!
| struct UserSession: Codable, Identifiable { | ||
| let id: Int | ||
| let nickname: String | ||
| let profileURL: String | ||
| let isPushAlarmAllowed: Bool | ||
| let isAdmin: Bool | ||
| let isAutoLoginEnabled: Bool? | ||
| let notificationBadgeCount: Int? | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
로그인한 유저 정보를 다룬다는 점에서 UserSession 이라는 네이밍은 너무나도 탁월한 것 같습니다.
저라면 LoginUserInfo 이런 식으로 다룰 것 같았거든요. 대단하십니다. 👍
자동 로그인 여부는 따로 서버에서 내려주지 않는 것으로 알고 있는데 클라이언트에서 처리하는 값인지 이후 따로 받아와야 하는지 확인되지 않아 일단 옵셔널 처리했습니다. 의견 주시면 감사하겠습니다 !!
제가 알기로는 자동로그인 정보는 서버에서 따로 내려주지 않는 것으로 알고 있습니다.
안드로이드 파트에서 자체 판단하에 구현한 것으로 알고 있는데요.
왜 구현했는지, 어떤 근거를 가지고 판단하는지 등의 로직은 안드로이드 개발자분들께 여쭤봐야 할 것 같습니다.
| protocol UserSessionStorage { | ||
| func fetchAllUserSessions() -> [String: UserSession] | ||
| func fetchUserSession(forUserID userID: String) -> UserSession? | ||
| func fetchActiveUserSession() -> UserSession? | ||
| func fetchActiveUserID() -> String? | ||
| func updateUserSession(_ session: UserSession, forUserID userID: String) | ||
| func updateAutoLogin(enabled: Bool, forUserID userID: String) | ||
| func updateNotificationBadge(count: Int, forUserID userID: String) | ||
| func updateActiveUserID(forUserID userID: String?) | ||
| func removeUserSession(forUserID userID: String) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
좋은 프로토콜 추상화인 것 같습니다.
Swift 언어의 특징인 POP를 잘 따르고 계시는군요!
| guard let data = defaults.data(forKey: Keys.userSessions), | ||
| let sessions = try? JSONDecoder().decode([String: UserSession].self, from: data) else { | ||
| return [:] | ||
| } | ||
| return sessions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
guard 구문에서 조건문이 2가지 이상일 경우, else는 무조건 리턴하기로 약속했던 것을 잠시 깜빡하신 것 같아요.
JSONDecoder는 메서드가 불릴 때마다 생성될 것 같은데요.
이러한 방법 보다는 프로퍼티로 소유하고, 이를 사용하는 형태면 메모리를 절약할 수 있을 것 같아요.
| if let data = try? JSONEncoder().encode(sessions) { | ||
| defaults.set(data, forKey: Keys.userSessions) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JSONEncoder도 프로퍼티로 가진다면, 매번 생성될 필요가 없어지겠죠? ㅎㅎ
| private enum Keys { | ||
| static let userSessions = "sessionDictionary" | ||
| static let activeUserID = "activeID" | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UserDefaults 저장을 위한 키를 String으로 사용하지 않은 점 최고에요! 👍
JinUng41
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이즈 굳~
키체인과 유저 디폴츠를 하나로 묶어 추상화를 구현하시다니 대단하십니다.
몇 가지 코멘트만 확인 부탁드려요.
고생하셨습니다.
| init(userDefaults: UserDefaults, jsonEncoder: JSONEncoder, jsonDecoder: JSONDecoder) { | ||
| self.userDefaults = userDefaults |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
init(userDefaults: UserDefaults = .standard, ...) 정도는 구현해도 좋을 것 같아요.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
반영했습니다!
| func removeValue(for key: String) throws { | ||
| guard userDefaults.object(forKey: key) != nil else { | ||
| throw LocalError.dataNotFound | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
좋은 에러 추상화네요~
| protocol LocalStorage { | ||
| func setValue<T: Codable>(_ value: T, for key: String) throws | ||
| func getValue<T: Codable>(for key: String) throws -> T? | ||
| func removeValue(for key: String) throws | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
지금은 CoreData 등을 사용하고 있지 않지만, 저는 그럼에도 메서드의 기능이 Key-Value의 형식으로 동작하는 것처럼 보여서, LocalKeyValueStorage로 네이밍을 변경하는 것은 어떨까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
반영했습니다! 좋은 의견 감사합니다
[Feat] UserDefaults 정보 저장을 위한 UserSession 기능 구현
👻 PULL REQUEST
📄 작업 내용
UserSessionStorage,UserSessionWrapper,UserSession을 각각 구현했어요.💻 주요 코드 설명
UserSession을 이용한 로컬 정보 저장 구조체 구현UserSession구조체를 선언했습니다.UserDefaults에 저장하기 위해 인코딩, 디코딩이 지원되어야 하므로Codable프로토콜을 채택했습니다.Identifiable프로토콜도 함께 채택했습니다.UserSessionStorage를 사용한UserSessionWrapper인터페이스 정의UserSessionWrapper에서 구현될 메서드들의 인터페이스를 정의했습니다.UserSessionWrapper를 사용한UserSession관리 로직 구현UserSessionStorage에서 정의한 인터페이스를 구현했습니다.UserDefaults에 접근할 때 사용할 키 값을Keys라는enum으로 정의했습니다.👀 기타 더 이야기해볼 점
🔗 연결된 이슈