아키텍쳐 설계
- 소프트웨어에서 아키텍처는 소프트웨어의 골격이 되는 기본 구조를 의미하며, 다음과 같은 특징을 가짐
- 소프트웨어의 골격을 나타내는 추상화된 전체 구조 제공
- 소프트웨어를 이루고 있는 여러 구성 요소를 다룸
- 인터페이스를 통해 소프트웨어의 구성 요소가 어떻게 상호작용하는지 정의
- 세부 내용보다는 중요내용만 다룸
- 설계에 적용되는 원칙과 지침이 있음
- 소프트웨어의 품질을 향상시키기 위해 올바른 소프트웨어 아키텍쳐의 설계가 필요함
- 소프트웨어 아키텍쳐를 올바르게 설계하기 위해 다음 기능을 고려해야 함
- 모든 이해관계자 사이의 의사소통 도구로 활용할 수 있어야 함
- 구현에 대한 제약 사항을 정의해야 함
- 모든 이해관계자의 품질 요구사항을 반영해 우선순위에 따라 시스템 품질 속성을 결정해야 함
- 특정 문제 영역에 적합한 소프트웨어의 구성 요소를 표준화하고 패턴화해 재사용할 수 있도록 설계해야 함
- 품질 속성으로는 시스템 품질 속성, 비즈니스 품질 속성, 아키텍쳐 품질 속성이 있음
시스템 품질 속성으로는 가용성(시스템이 오류없이 서비스를 제공할 수 있는 능력), 변경 용이성, 성능, 보안성, 사용성, 테스트 용이성이 있음
비즈니스 품질 속성으로는 시장 적시성, 비용과 이익, 예상 시스템 수명, 목표 시장, 발매 일정, 기존 시스템과의 통합 등이 있음
아키텍쳐 품질 속성으로는 개념적 무결성(=일관성), 정확성과 완전성, 개발 용이성이 있음
아키텍쳐의 4+1 관점

- 소프트웨어 아키텍쳐를 설명하는 관점
- 시나리오 관점은 최종 사용자가 인식하는 기능으로써, 기능 하나하나가 유스케이스로 표현되기 떄문에 유스케이스 관점으로도 볼 수 있음
시나리오 관점에서 작성된 유스케이스 다이어그램은 다른 4개 관점에서 사용하는 다이어그램의 근간이 됨
- 논리적 관점은 시스템의 기능을 제공하기 위해 필요한 클래스, 컴포넌트 및 관계에 초점을 둠
- 프로세스 관점은 논리적 관점과 같이 시스템 내부 구조에 초점을 맞추지만, 독제적인 제어 스레드에 초점을 두어 시스템의 프로세스, 스레드 사이의 관계를 표현함
- 구현 관점은 코드, 데이터 파일, 실행 파일 등의 소프트웨어 서브시스템의 모듈사이의 관계를 나타냄
- 배치 관점은 서브시스템이 물리적인 환경에서 어떻게 배치, 연결, 실행되는지를 나타냄
아키텍쳐 스타일
데이터 중심형 스타일

- 데이터를 리포지토리에 공동으로 보관하고 공유 데이터를 서브시스템이 사용하는 방법
- 데이터를 모순되지 않고 일관성 있게 관리할 수 있고, 새로운 서브시스템을 추가하기도 쉬움
- 리포지토리가 병목현상을 일으킬 수 있고, 리포지토리 변경시 서브시스템에 영향을 줄 수 있음
클라이언트-서버 스타일

데이터와 처리기능을 클라이언트와 서버에 분할해 사용하는 방법
- 서버는 클라이언트에 서비스를 제공하는 역할을 함
- 클라이언트는 사용자와 대화하기 위해 서버가 제공하는 서비스를 호출하는 서브시스템
- 터미널과 같이 모든 응용 처리 및 데이터 관리를 서버에서 수행하고 클라이언트는 presentation만 구현하는 경우를 씬 클라이언트 스타일로 부름
유지보수에 유리하고 프로그램이 가벼워 지지만, 서버와 네트워크에 걸리는 부하가 큼
- 리액트 라이브러리처럼 서버에서 데이터 관리만 관여하고 애플리케이션 로직과 presentation의 상당수가 클라이언트 쪽에서 구현되는 것을 팻 클라이언트 스타일이라 함
- 서버와 네트워크의 부담을 줄일 수 있지만, 씬 클라이언트 스타일보다 관리하기 복잡하고 업데이트 시 모든 클라이언트에 배포를 해줘야 한다는 단점이 있음
계층 스타일

- 하나의 계층을 하나의 서브시스템으로 생각하고 하위 계층을 서버 역할, 상위 계층을 클라이언트의 역할을 하도록 구성함
- 데이터 계층에서는 데이터베이스에 접근해 데이터 처리와 관리를 하며, 백엔드 부분으로도 부름
- 비즈니스 로직 계층은 기능 요구사항을 구현하는 곳으로 미들웨어라고도 부름
- 프리젠테이션 계층은 클라이언트 계층으로서 프론트엔드라고도 부름
- 계층 내부의 응집도는 높고 계층간 결합도가 낮아 재사용 및 유지보수가 용이함
- 대부분의 운영체제와 통신 시스템이 계층 스타일 사용
MVC 스타일

- 모듈을 나눠 Model, View, Controller의 세 부분으로 나누어 관리하는 방법
- Model은 모든 데이터 상태와 로직을 처리하는 부분으로, 특정 입출력 방식에 영향을 받지 않고, 호출에 응답을 함
- View는 사용자에게 데이터를 직접 보여주는 부분
- Controller는 View를 통한 사용자 요청을 적절한 모델에 넘겨주고, 모델로 받은 응답을 다시 View를 통해 사용자에게 돌려주는 부분
데이터 흐름 스타일

- 필터에 해당하는 하나의 서브시스템이 하나의 데이터를 입력으로 받아 처리하고 그 결과를 다음 시스템에 넘기는 과정을 반복하는 스타일
- 데이터 스트림을 입력으로 받아 데이터 스트림 하나를 출력하는 부분을 필터, 필터의 출력을 다른 필터의 입력에 연결하는 것을 파이프라고 함
클래스 간의 관계
연관 관계
- 클래스 간에 서로 메시지를 주고받으며 사용하는 관계
- 한 클래스가 상대 클레스에서 제공하는 기능을 사용하며, 클래스는 상대 클래스를 인식하고 있고, 상대 클레스의 변수를 받아 사용하게 됨
세부적으로는 아래와 같이 구분 가능
- 양방향 연관 관계: 학생과 교수가 수업이라는 관계로 엮이듯이 두 객체와 관계만으로 구성된 연관 관계, 두 클래스는 서로 반대 클래스가 무엇인지를 알고 있음
- 다중 연관 관계: 양방향 연관 관계와 비슷하나 각 그룹의 객체가 한명씩이 아닌 경우(2:1, 다대다 등등)의 연관 관계
- 단방향 연관 관계: 두 클래스 그룹간의 연관이 있는 것은 같지만, 한쪽은 상대 클래스를 알고, 다른 한쪽은 상대 클래스를 모르는 관계
- 연관 클래스: 연관관계를 구체적으로 나타내기 위해 점선을 사용해 클래스를 추가로 보여주는 것

일반화 관계

- 여러 클래스 사이의 일반적인 공통점을 찾아내 공통된 클래스를 만드는 방법
- 일반화 관계는 상속 구조이며 하위 클래스는 상위 클레스의 모든 것을 상속받아 사용함
- 개별 클래스와 공통 클래스 사이에 is a kind of 관계가 성립되어야 함
ex: 삼각형은 도형의 일종이다, 사각형은 도형의 일종이다에서 삼각형, 사각형과 도형의 관계
집합 관계
- 상위 클래스가 하위 클래스로 구성되는 경우
- 개별 클래스와 공통 클래스 사이에 is composed of 관계가 성립해야 함
ex: 모니터, 본체, 키보드는 컴퓨터의 구성요소
- 모든 객체가 별도의 생명주기를 가지고 있으며, 각각 독립적으로 동작 -> 약한 결합 관계

합성 관계
- 집합 관계와 비슷하지만, 각각의 요소를 분리해 재사용할 수 없는 경우
- 부분 객체는 전체 객체가 없어질 때 같이 없어지게 됨 -> 강한 결합 관계

의존 관계
- 연관 관계와 비슷하지만 상대 클래스를 인식하기 위해 변수를 클래스 전역 시간동안 가지지 않는 경우
- 메서드에 상대 클래스를 인자로 가지거나 메서드 내부에서 지역 변수로 클래스를 선언하는 경우 메서드 실행 동안만 해당 변수를 가지게 되며, 이 경우를 의존 관계로 부름
class multiplier {
multiply (a: number, b: number) {
return a * b;
}
}
class squareCalculator {
const innerMultiplier = new multiplier();
square(a: number) {
return innerMultiplier.multiplier(a, a);
}
}
class squareCalculator {
square (a: number, multiplier: multiplier) {
return multiplier.multiply(a, a);
}
}
class squareCalculator {
square (a: number) {
const innerMultiplier = new multiplier();
return innerMultiplier.multiplier(a, a);
}
}
실체화 관계

- 인터페이스와 같이 추상 메서드를 하위 클래스에서 구체적으로 구현하는 관계
- 객체마다 구현이 조금씩 달라지는 경우에 사용 가능한 관계임
클래스 설계 원칙
- 단일 책임 원칙(Sing-Responsibility Principle): 클래스를 변경해야 하는 이유는 단 하나여야 한다. => 하나의 클래스는 하나의 책임만을 가져야 함
- 개방 폐쇄 원칙(Open-Closed Principle): 변경에는 닫혀 있어야 하고, 확장에는 열려 있어야 한다 => 추가 사항이 클래스에 영향을 받지 않도록 하고, 새로운 클래스는 쉽게 추가할 수 있는 구조여야 함
- 리스코프 교체 원칙(Liskov Subsitution Principle): 상위 클래스의 객체는 언제나 하위 클래스의 객체로 교체할 수 있어야 함 => 상속 구조를 만들 때 상위 클래스가 추상 클래스, 추상 메서드여야 하고 오버라이딩을 할 때는 피터 코드의 상속 규칙에 맞게 사용해야 함
피터 코드의 상속 규칙: 하위 클래스는 상위 클래스의 책임을 무시하거나 재정의하지 않고 확장만 수행함
- 인터페이스 분리 원칙(Interface Segreation Principle): 클라이언트는 자신이 사용하지 않는 메서드와 의존 관계를 맺으면 안 된다. => 사용하지 않은 인터페이스 때문에 영향을 받아서는 안됨
- 의존 관계 역전 원칙(Dependency Inversion Principle): 클라이언트는 구체 클래스가 아닌 추상 클래스에 의존해야 한다 => 서로다른 여러 객체가 동일한 구체 클래스를 참조하게 해서는 안됨
출처:
쉽게 배우는 소프트웨어 공학 2판, 김치수, 한빛아카데미