도메인 모델을 구현하는 방법 중 자주 사용되는 것이 DB 엔티티 객체를 만들어 사용하는 방식이다. ORM을 통해 엔티티를 사용하면 UML 등으로 객체지향적으로 설계한 내용을 실제 구현체에 반영하기 쉽다. 하지만 객체를 이용하는 것에도 단점이 있다. 그것은 바로 수정하기 쉽다는 것이다. 무심코 Setter를 만들어버리면 메서드 하나만 호출하면 값을 쉽게 바꿀 수 있기 때문에 원치 않는 결과를 초래할 수 있다. 나도 전부터 여러 프로젝트를 진행하면서 무심코 Setter를 만들어 엔티티의 값을 수정하곤 했는데 이번에는 필요한 부분만 메서드로 따로 만들어 리팩토링 하기로 했다.
Badge 엔티티
@Setter
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Table(name = "badges")
@Entity
public class Badge {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private BadgeEnum id;
private String description;
}
사용자가 얻을 수 있는 뱃지에 관한 엔티티이다.
id의 경우 그냥 Integer로 설정해도 됐지만, 다른 곳에서 뱃지를 가져올 때 매번 뱃지를 검색해서 가져오는 것보다 enum을 이용해 VO를 만들어 사용하는 것이 DB 조회도 줄일 수 있고 사용하기도 편해서 이렇게 설정했다.
뱃지 엔티티의 경우는 찾아보니 Setter를 사용하는 곳이 없었다. 그런데도 무심코 그냥 Setter를 만들어 버린 것이다. 그래서 Setter를 삭제했다.
Problem 엔티티
@Setter
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Table(name = "problems")
@Entity
public class Problem extends BaseTimeDate {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
@Enumerated
private Level level;
private Byte score;
private String params;
@Column(name = "return_type")
private String returnType;
private String description;
private String limitation;
@Column(name = "input_output")
private String inputOutput;
@Column(name = "solved_count")
private Long solvedCount;
@Column(name = "try_count")
private Long tryCount;
@Column(name = "correct_rate")
private String correctRate;
@OneToMany(mappedBy = "problem")
private Set<ProblemPicture> problemPictureList;
@OneToMany(mappedBy = "problem", cascade = CascadeType.PERSIST)
private Set<Testcase> testcaseList;
public void addSolvedCount() {
this.solvedCount++;
this.tryCount++;
reCalcCorrectRate();
}
public void addTryCount() {
this.tryCount++;
reCalcCorrectRate();
}
private void reCalcCorrectRate() {
this.correctRate = String.valueOf((Float.valueOf(solvedCount) / tryCount) * 100).split("\\.")[0];
}
}
사용자가 풀 수 있는 문제 관련 정보를 담고 있는 엔티티 객체이다.
위 엔티티에서는 일부 속성값의 변경을 위해 addSolveCount() 등의 메서드를 사용한다. 외부에서 따로 Getter와 Setter로 증가 처리를 하지 않아도 되어 괜찮은 방법이지만, 메서드 명이 내용에 비해 명확하지 않아 다음과 같이 변경했다.


변경 후, 어떤 동작인지 더 알기 쉬워졌다.
문제 엔티티도 Setter를 사용하는 곳이 없었는데 아마 내가 위 메서드를 사용하는 것으로 변경하면서 미처 지우지 못했던 것 같다.
이렇게 실수로라도 객체의 내용을 변경할 여지를 만들어두게 되면 나중에 처리하기 더 힘들어질 수도 있으므로 조심해야겠다.
User 엔티티
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Builder
@Table(name = "users")
@Entity
public class User extends BaseTimeDate {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Enumerated(EnumType.ORDINAL)
private Provider provider;
@Column(name = "provider_id")
private String providerId;
@Column(name = "provider_nickname")
private String providerNickname;
private String nickname;
@Column(name = "solve_count")
private Integer solveCount;
private Integer score;
@Enumerated(EnumType.ORDINAL)
private Role role;
@OneToMany(mappedBy = "user", cascade = CascadeType.PERSIST)
private List<UserBadge> userBadgeList;
@OneToMany(mappedBy = "user", cascade = CascadeType.PERSIST)
private List<UserProblem> userProblemList;
public void addScoreAndSolveCount(Byte problemScore) {
this.score += problemScore;
this.solveCount++;
}
}
유저 엔티티에서는 다른 곳에서 Setter를 사용하고 있었다.

사실 별명만 Setter를 사용해도 되지만 좀 더 유저 도메인에서의 행위에 맞게 updateNickname()이라고 변경했다.

UserProblem 엔티티에도 비슷한 부분이 있었지만 위 내용과 비슷해 생략했다.
그동안은 그냥 현재의 편함을 위해 별 생각없이 했던 행동들이 나중에 예기치 못한 상황을 발생시킬 수 있다는 것을 깨달았다. 앞으로는 도메인 분석에 더 큰 관심을 가지고 어떻게 하면 보기 좋고, 사용하기 좋고, 안전한 코드를 작성할 수 있을지 고민해 봐야겠다.
'공부 > Java' 카테고리의 다른 글
| [Java] 도메인 위주로 리팩토링하기 (0) | 2024.12.23 |
|---|---|
| [Java] 자바 Reflection(리플렉션)을 이용하여 동적 개수 파라미터 받아 문자열로 된 메서드 실행하기 (0) | 2024.11.01 |
| [Java] 문자열 그대로 클래스로 만들어 컴파일 후 실행하기 (0) | 2024.09.28 |
| [Java] 퀵정렬 (QuickSort) 구현하기 (0) | 2024.09.24 |
| [Java] 동적 프록시 (Dynamic Proxy) (0) | 2024.07.23 |