Clean-Architecutre #2부 - 제약으로 발전해온 코딩 패러다임

Clean-Architecutre #2부 - 제약으로 발전해온 코딩 패러다임

3가지 프로그래밍 패러다임

03. 패러다임 개요

패러다임에는 3가지 종류가 있다.

구조적 프로그래밍, 객체 지향 프로그래밍, 함수형 프로그래밍

  • 구조적 프로그래밍
    • 제어 흐름의 직접적인 전환에 대해 규칙을 부과한다.

다익스트라goto문의 해로움을 발견하고, if/then/elsedo/while과 같은 구조로 대체하면서 탄생했다.

  • 객체 지향 프로그래밍
    • 제어 흐름의 간접적인 전환에 대해 규칙을 부과한다.

클래스, 인스턴스, 메서드와 함께 다형성이 등장하면서 탄생했다.

  • 함수형 프로그래밍
    • 할당문에 대해 규칙을 부과한다.

람다 계산법을 기반으로 탄생했다. 불변성을 지키면서 프로그래밍을 하게 된다.

패러다임은 특정 행동들에 대한 제약, 규칙을 부과한다.
이 세 가지 패러다임이 아키텍쳐의 세 가지 큰 관심사(함수, 컴포넌트 분리, 데이터 관리)와 어떻게 연관되는지 파악해야 한다.


04. 구조적 프로그래밍

goto 문에서 원자적으로 순차, 분기, 반복인 if/then/elsedo/while/until/for과 같은 구조로 대체하면서 탄생했다.
이를 통해 아무리 거대한 요구사항이라도 고수준의 기능으로 분해할 수 있게 되었다.
또한 고수준의 기능도 세부적인 저수준의 구현체들로 분해할 수 있게 되었다.
이렇게 분해된 기능들은 증명 가능한 단위가 되고, 이를 통해 테스트를 통해 기능들이 거짓인지를 증명할 수 있게 되었다.

흥미로운 점은 프로그래밍이 수학적 접근이 아닌 과학적 접근이라는 것이다.
TDD는 증명 가능한 단위로 코드를 분해하고, 테스트를 통해 기능들이 거짓인지를 증명하는 것이다.
우리는 프로그램이 완벽한 환경에서 항상 돌아가는것이 아닌, 테스트를 통해 증명한 범위안에서 돌아가는 것을 보장한다.

  • 구조적 프로그램이 제공하는 가치
    • 프로그램을 증명 가능한 단위로 분해할 수 있다.
    • 테스트를 통해 증명 가능한 단위가 거짓인지를 증명할 수 있다.

05. 객체지향 프로그래밍

캡슐화, 상속, 다형성을 통해 객체를 만들고, 객체들을 조합하여 프로그램을 만드는 것이다.

🌟 캡슐화

객체의 내부 변수는 감추고 일부 허용된 메서드만 외부에 노출시킨다.

public class Person {
    private String name;
    private int age;
     
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
     
    public String getName() {
        return name;
    }
     
    public int getAge() {
        return age;
    }
}

위의 코드에서 nameage는 외부에서 접근할 수 없다.
getName()getAge()를 통해서만 접근할 수 있다.
사용자들은 Person 객체가 주는 Public 메서드인 기능 만으로 객체를 사용할 수 있다.
즉, Person 내부 구현을 자유롭게 변경해도 외부에서 Person을 사용한 코드는 변경에 영향을 받지 않는다.

🌟 상속

상속은 단순히 어떤 변수와 함수를 하나의 유효 범위로 묶어서 재정의하는 일에 불과하다.

🌟 다형성

함수를 가리키는 포인터를 응용한 것이 다형성이다.
OO언어는 좀 더 안전하고 편리하게 사용할 수 있게 해주는데 포인터를 직접 사용하는 관례를 없애주며 실수할 위험이 없다.

  • 다형성이 가진 힘
    • 기존 구현의 변경 없이 새로운 기능을 추가할 수 있다.
    • 플러그인 스타일로 고수준 객체에서 저수준 객체를 분리할 수 있다.
    • 기존 코드 자체에서는 이미 고수준 객체로 동작 에 초점을 맞춰 구현했기 때문에, 저수준 객체를 변경해주면 기존코드 수정 없이 기능 변경이 가능하다.
  • 어떻게 고수준에서 저수준을 분리하여 기능만 명명해 사용이 가능하게 됐을까? -> 의존성 역전

기존에 저수준 객체를 사용하기 위해서는 고수준 객체가 저수준 객체를 직접 참조해야 했다.
제어 흐름은 위에서 아래로 고정되어있을 수 밖에 없었다. ex)Java의 import, C언어의 include 등
그러나 Interface가 등장하면서 고수준 모듈에서 기능을 선언하여 사용하고, 저수준 모듈에서 구현이 명명된 고수준 Interface를 참조하게 된다.
이를 통해 제어 흐름이 역전되어 저수준 모듈이 고수준 모듈에 의존하게 된다.(방향이 반대)

의존성 역전으로 각각의 컴포넌트가 서로 독립적으로 존재할 수 있게 된다. 일종의 객체 캡슐화랑 의도와 맥락이 비슷하다.
(UI, DB, APP등이 분리하여 독립적으로 존재할 수 있다.)

🌟 결론

객체지향 프로그래밍은 다형성을 이용하여 고수준의 기능을 저수준의 구현체로부터 분리할 수 있게 해준다.
이를 통해 플러그인처럼 기능을 분리하여 저수준 모듈의 변경으로부터 자유로워진다.
저수준의 세부사항은 플러그인 모듈로 제작할 수 있고, 고수준의 정책을 포함하는 모듈에 독립적으로 개발하고 배포할 수 있다.


06. 함수형

함수형 프로그래밍에서 핵심 논제는 변수는 불변성을 가진다 이다. 변수가 가변적이면 프로그램 설계시 많은 고려사항이 있다.

  • 변수가 변할 가능성은 경합조건, 교착상태. 동시 업데이트 등 많은 문제를 야기한다.

그러나 우리는 모든 변수를 불변하게 유지할 수 없다. 그렇다면 어떻게 해야할까?

🌟 가변성의 분리

불변성과 관련하여 중요한 타협점은 가변 컴포넌트와 불변 컴포넌트로 분리하는 일이다.
불변 컴포넌트에서는 함수형 방식으로 작업이 처리되며, 어떤 가변 변수도 사용되지 않는다.
가변 컴포넌트에서는 객체지향 방식으로 작업이 처리되며, 불변 변수를 사용할 수 있다.

현명한 아키텍트라면 가능한 많은 부분을 불변 컴포넌트로 분리하고, 가변 컴포넌트는 최소한으로 줄여야 한다.

🌟 이벤트 소싱

저장과 처리공간에 대한 제약이 사라지면서, 실 데이터를 Update, Delete하는 것이 아닌, Select, Insert만 하는 방식이 등장했다.
이벤트 소싱은 상태가 아닌 트랜잭션을 저장하여 상태가 필요해지는 순간 초기 상태에서 모든 실행된 트랜잭션을 추적하여 현재 값을 파악한다.

ex) 예를 들어 은행 계좌가 있다고 가정하자. 계좌에 입금, 출금, 이체 등의 트랜잭션이 발생한다.

이벤트 소싱은 계좌의 현재 잔액을 저장하는 것이 아닌, 계좌에 발생한 트랜잭션을 저장한다.

결과적으로 어플리케이션은 CRUD에서 CR만 수행한다.
핵심은 이런 상태에서 모든 값들은 불변성을 가지게 된다.

Event 트랜잭션을 저장하는 객체는 단순히 발생한 출금,입금 행위여서 불변성을 가지게 된다.
추후에 필요할 때 상태를 계산한 후 저장하면 된다.
데이터 저장소에서 변경과 삭제가 전혀 발생하지 않으므로 동시성, 경합조건, 교착상태 등의 문제가 발생하지 않는다.