객체지향 프로그래밍으로 SNS 도메인 설계하기

실제 코드로 분석하는 OOP 원칙과 도메인 모델링

객체지향 프로그래밍을 공부하다 보면
‘추상화’, ‘캡슐화’, ‘상속’, ‘다형성’, 그리고 ‘SOLID 원칙’과 같은 개념을 이해하는 것이 쉽지 않다.
이론으로만 접하면 감이 잘 오지 않지만, 실제 코드 속에서 이러한 원리가 어떻게 적용되는지 살펴보면 훨씬 명확하게 이해할 수 있다.

이 글에서는 SNS(Post, Comment, User) 도메인을 직접 설계하면서 적용된 객체지향 개념들을 정리한다.
각 도메인 클래스에 어떤 의도가 담겨 있고, 어떤 객체지향 원칙을 따르고 있는지 실제 코드와 함께 분석해보자.


1. 도메인을 객체로 표현하는 과정

객체지향 설계의 첫 단계는 현실 세계의 개념을 객체로 추상화하는 것이다.

SNS 서비스에는 다음과 같은 개념이 존재한다.

  • 사용자(User)
  • 글(Post)
  • 댓글(Comment)
  • 텍스트 내용(Content)
  • 좋아요 수(like counter)
  • 작성/수정 날짜(datetime)

이 개념들을 그대로 객체로 변환한 것이 이 프로젝트의 시작점이다.


2. 캡슐화(Encapsulation) — 데이터는 감추고 행동만 드러낸다

객체지향의 핵심은 캡슐화다.
즉, 데이터는 외부에서 직접 접근할 수 없도록 숨기고, 행동(메서드)을 통해서만 객체를 조작하게 만든다.

다음 코드를 보자.

public class PositiveIntegerCounter {
    private int count;

    public void increment() {
        this.count++;
    }

    public void decrement() {
        if (this.count <= 0) {
            return;
        }
        this.count--;
    }
}

이 클래스는 좋아요 수, 팔로워 수 등에서 공통으로 사용하는 카운터 도메인이다.

핵심은 다음과 같다.

  • count 필드는 private이므로 외부에서 직접 조작할 수 없다.
  • 카운터의 변경은 반드시 increment()decrement()를 통해서만 이루어진다.
  • 음수 상태가 되지 않도록 객체가 스스로 보호한다.

이처럼 객체가 스스로 자신의 유효 상태를 관리하는 것이 캡슐화다.


3. 추상화(Abstraction) — 공통 개념을 상위 클래스로 묶는다

SNS 글(Post)과 댓글(Comment)은 모두 텍스트 기반 콘텐츠이며,
작성/수정 시간 기록이 필요하다는 공통점이 있다.
반면 길이 제한은 서로 다르다.

이 특징을 반영하기 위해 Content라는 추상 클래스를 만들었다.

public abstract class Content {
    String contentText;
    final DatetimeInfo datetimeInfo;

    protected Content(String contentText) {
        checkText(contentText);
        this.contentText = contentText;
        this.datetimeInfo = new DatetimeInfo();
    }

    public void updateContentText(String contentText) {
        checkText(contentText);
        this.contentText = contentText;
        this.datetimeInfo.updateEditDateTime();
    }

    protected abstract void checkText(String contentText);
}

그리고 이를 확장하는 두 개의 구현체가 존재한다.

PostContent

public class PostContent extends Content {
    private static final int MIN_POST_LENGTH = 5;
    private static final int MAX_POST_LENGTH = 500;

    @Override
    protected void checkText(String contentText) { ... }
}

CommentContent

public class CommentContent extends Content {
    private static final int MIN_COMMENT_LENGTH = 1;
    private static final int MAX_COMMENT_LENGTH = 280;

    @Override
    protected void checkText(String contentText) { ... }
}

핵심 개념은 다음과 같다.

  • 공통 속성과 기능은 Content에 정의한다.
  • 길이 규칙처럼 서로 다른 부분은 자식 클래스에서 구현한다.
  • 이는 전형적인 템플릿 메서드 패턴(Template Method Pattern) 구조다.

4. 불변성(Immutable) 유지 — 바뀌면 안 되는 값은 final로 보호한다

도메인 객체에서 어떤 값은 절대 바뀌면 안 된다.
예를 들어 Post의 작성자(author)나 id 값은 변경되면 도메인 자체가 깨진다.

private final Long id;
private final User author;
private final PostContent content;

이처럼 불변성을 보장하면 다음과 같은 장점이 있다.

  • 도메인의 일관성이 유지된다.
  • 스레드 안전성이 높아진다.
  • 의도치 않은 부작용을 방지할 수 있다.

5. 도메인 규칙을 객체 자신이 책임지도록 만든다

Post.like() 메서드를 보자.

public void like(User user) {
    if (this.author.equals(user)) {
        throw new IllegalArgumentException("User cannot like their own post");
    }
    likeCounter.increment();
}

이 규칙은 PostService가 아닌 Post 객체 내부에서 처리된다.

왜 그럴까?

도메인 규칙은 가장 관련 있는 객체가 지켜야 한다.
좋아요 규칙은 Post 도메인의 규칙이므로 Post가 직접 책임지는 것이 옳다.
이는 DDD(Rich Domain Model) 설계 원칙과도 일치한다.


6. 디미터의 법칙(Law of Demeter) — 객체 내부 구조를 외부에 노출하지 않는다

다음은 User.follow()의 일부 코드다.

public void follow(User targetUser) {
    if (targetUser.equals(this)) {
        throw new IllegalArgumentException("User cannot follow itself");
    }

    followingCounter.increment();  
    targetUser.increaseFollowerCount();
}

여기서 중요한 점은 다음과 같다.

// 이렇게 하면 안 된다
targetUser.followerCounter.increment();

필드를 외부에서 직접 조작하면 캡슐화가 깨진다.

따라서 User는 followerCount를 변경하는 로직을 자신의 내부에서만 수행한다.

private void increaseFollowerCount() {
    followerCounter.increment();
}

이것이 바로 디미터의 법칙,
즉 **“다른 객체의 내부 구조에 의존하지 말라”**는 원칙이다.


7. 상속(Inheritance)과 다형성(Polymorphism)의 자연스러운 적용

Content 추상 클래스는 상속과 다형성을 보여주는 좋은 예다.

  • PostContent와 CommentContent는 각각 다르게 검증 로직을 갖는다.
  • Content를 참조하는 입장에서는 두 타입을 모두 동일하게 다룰 수 있다.
  • 공통 기능은 상위 클래스에 모이고, 확장 포인트는 하위 클래스에서 구현된다.

이 구조 덕분에 기능 확장이 매우 쉽다.
예를 들어 DMContent, ArticleContent 등이 추가되어도 Content 구조가 충분히 수용할 수 있다.


8. 객체 간 관계는 현실 세계의 구조를 반영한다

도메인 간의 관계도 객체지향적으로 잘 설계되어 있다.

  • User는 여러 개의 Post를 작성한다.
  • Post는 여러 개의 Comment를 가진다.
  • User는 다른 User를 follow할 수 있다.

이 구조는 현실 모델과 동일하며,
이를 객체 간 연관 관계로 자연스럽게 옮겨온 것이다.


결론: 이 도메인 구조는 객체지향 원칙이 명확하게 반영된 모델이다

이 프로젝트는 다음과 같은 객체지향 특징을 모두 갖추고 있다.

  • 캡슐화
  • 추상화
  • 상속과 다형성
  • 불변성 유지
  • 디미터의 법칙 준수
  • 도메인 규칙을 객체 내부에서 처리
  • 응집도 높은 구조
  • 중복 제거를 위한 공통 도메인(PositiveIntegerCounter)
  • DDD 스타일의 풍부한 도메인 모델(Rich Domain Model)

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다