[Laravel9] 쉽게 라라벨 DI, IOC 이해하기

2023. 2. 16. 16:16· PHP/Laravel
목차
  1. DI, IOC란
  2. DI
  3. IOC
  4. DI, IOC 사용 이점
  5. 후기

DI, IOC란

DI, IOC 단어는 스프링과 라라벨 같은 웹 프레임워크를 하다 보면 알아야 하는 개념 중 하나입니다.

해당 포스트에서는 DI, IOC 개념을 알 수 있습니다.

 

DI

Dependency Injection(의존성 주입)은 객체의 의존 관계를 외부에서 주입한다.

여기서 의존 관계는 아래 코드로 알 수 있습니다.

class AClass{
    function test(){
        echo "test";
    }
}

class BClass{
    private $internalAClass;

    public function __construct(AClass $internalAClass){
        $this->internalAClass = $internalAClass;
    }

    public function getATest(){
        echo $this->internalAClass->test();
    }
}

$BClass = new BClass(new AClass());
$BClass->getATest();

지금 BClass의 getATest를 사용하기 위해서는 AClass의 인스턴스를 받아서 생성자로 할당하는 작업이 필요합니다.

이때 BClass는 AClass에 의존관계를 가지게 됩니다.

 

의존성주입이란 이 작업을 외부에서 주입을 해주는 것을 의미합니다.

일반적으로는 3가지 방법이 있습니다.

  • 생성자 주입 : 생성자로 주입, (위의 쓴 예제가 생성자 주입)
  • 세터 주입 : Setter 메서드를 선언하여 주입
  • 메서드 주입 : Setter가 아닌 메서드를 작성하여 주입

 

만약 위 예제에서 의존관계가 많아진다면 코드 수정의 작업 수가 많아질 것입니다.

class BClass{
    private $internalAClass;
    private $internalA1Class;
    private $internalA2Class;
    private $internalA3Class;

    public function __construct(
        AClass $internalAClass,
        A1Class $internalA1Class,
        A2Class $internalA2Class,
        A3Class $internalA3Class,
    ){
        $this->internalAClass = $internalAClass;
        $this->internalA1Class = $internalA1Class;
        $this->internalA2Class = $internalA2Class;
        $this->internalA3Class = $internalA3Class;
    }

    ...
}

$BClass = new BClass(
    new AClass()
    new A1Class()
    new A2Class()
    new A3Class()
);

위와 같이 사용할 경우 BClass를 사용하는 부분마다 수많은 AClass와의 의존관계를 넣어줘야 하게 됩니다.

 

※ 클라이언트 : 여기서는 대상 로직을 실행하는 로직을 의미

여러 웹 프레임워크에서는 이를 알아서 의존 관계 주입을 해주는데

라라벨은 컨테이너가 클래스의 생성자의 타입힌트를 읽어 인스턴스를 자동 오토와이어링해줍니다.

 

기존 라라벨로 개발해 봤던 분들은 Controller 클래스에서 인스턴스를 생략했던 게 기억나실 텐데 

그럴 수 있던 이유가 라라벨에서 자동으로 의존성 주입(DI)을 해주기 때문입니다.

 

Contoller 클래스 예시

class UserController extends Controller
{
  
    protected $users;

    // 생성자에서 UserRepository를 오토와이어링
    public function __construct(UserRepository $users)
    {
        $this->users = $users;
    }

    public function show($id)
    {
        $user = $this->users->find($id);
        return view('user.profile', ['user' => $user]);
    }
}

 

의존성 주입을 하게 되면 아래의 이점들을 얻을 수 있습니다.

  • 코드 재사용을 높여서 소스 코드의 수정 없이 사용
  • 의존성 주입부를 모의 객체로 바꾸기 쉽기에 테스트의 편리성을 높임
  • 의존 관계 설정이 실행 시에 이루어져 모듈들 간의 결합도를 낮춰 유연한 코드 개발이 가능
  • 클라이언트는 의존관계 객체 내부의 구체적인 기능을 몰라도 되기에 유지보수성을 높임

 

IOC

Inversion of Control(제어의 역전)은 개발자가 직접 컨트롤하는 게 아닌 프레임워크가 호출한다.

DI 설명에서 프레임워크에서 DI를 해준다 하였는데 그게 IOC의 개념입니다.

 

프레임워크가 객체(대부분 Singleton을 사용한 객체)의 생성부터 주입(DI), 변경, 소멸까지의 생명주기(Life Cycle)를  관리하여 개발자로 하여금 개발에 집중에만 집중할 수 있게 합니다.

 

IOC를 사용하면 좋은 점은 객체지향 설계의 5원칙의 OCP, DIP 지키기 용이해집니다

 

SOLID : [OCP, DIP]

  • OCP(Open/closed principle) : 개방-폐쇄 원칙
    소프트웨어는 확장은 자유로우나 수정에는 닫혀있어야 한다.
  • DIP(Dependency inversion principle) : 의존관계 역전 원칙
    클라이언트는 구현체가 아닌 인터페이스에 의존해야 한다.

나머지는 아래 주소 참고

https://seung.tistory.com/entry/Spring-%EC%8A%A4%ED%94%84%EB%A7%81-%EC%8A%A4%ED%94%84%EB%A7%81%EA%B3%BC-SOLID-5%EC%9B%90%EC%B9%99

 

정의만을 보았을 때는 알 수 없습니다, 실제 코드로 이를 적용하기는 까다롭습니다.

하지만 이 점을 알고 있는 대부분의 웹 프레임워크가 이점을 해결했기에 프레임워크 사용 시 쉽게 적용 가능합니다.

 

라라벨에서는 아래 예시처럼 하면 OCP, DIP 원칙을 적용할 수 있습니다.

 

관련 예제를 만드는데

아래 순서대로 진행합니다.

  1. 인터페이스 생성
  2. 구현체 생성
  3. Provider 등록 
  4. IOC 적용

인터페이스 생성

Repository Interface를 만들어줍시다, Repository라 한 이유는 Repository 패턴을 사용하기 때문입니다.

주제가 IOC, DI이므로 다음에 포스트에서 정리하겠습니다.

※ Repository 패턴 : 모델의 데이터베이스 관련 기능을 Repository로 빼고 모델은 모델끼리의 관계나 Accessor 등을 지님
Reposiotry는 쉽게 생각하면 DAO

namespace App\Repositories\Interfaces;

interface UserRepositoryInterface extends BaseRepositoryInterface
{
    /**
     * 유저 아이디로 데이터 찾기
     * @param $id : user id
     * @return mixed
     */
    public function getOneById($id);
}

 

구현체 생성

Repository Interface를 구현한 UserRepository를 만들어줍시다.

namespace App\Repositories\Implementations;

use App\Models\User;
use App\Repositories\Interfaces\UserRepositoryInterface;

class UserRepository implements UserRepositoryInterface
{
    private $model;
	
    // User 모델로 오토와이어링
    public function __construct(User $model)
    {
        $this->model = $model;
    }

    public function getOneById($id)
    {
        return $this->model->find($id);
    }
 }

 

Provider 생성

라라벨에서 인터페이스와 구현체를 찾을 수 있도록 바인딩해줍니다.

app\Providers\RepositoryProvider.php를 만들어서 UserRepositoryInterface::class에 UserRepository::class를 싱글톤으로 바인딩해줍니다.

※ 싱글톤 : 바인딩마다 새로운 인스턴스를 만드는 게 아닌 캐싱된 인스턴스를 사용, 주의점으로는 싱글톤은 무상태여야 함

class RepositoryProvider extends ServiceProvider
{
    public function register()
    {
        $this->registerResponseBindings();
    }

    protected function registerResponseBindings()
    {
        $this->app->singleton(UserRepositoryInterface::class, UserRepository::class);
    }

    public function boot()
    {
        //
    }
}

config\app.php의 providers에 추가합니다.

    'providers' => [
		...
        App\Providers\RepositoryProvider::class,
    ],

이제 UserRepositoryInterface는 UserRepository 바인딩되었으므로 내부 컨테이너에서 오토와이어링 할 때

UserRepositoryInterface을 만나면 UserRepository로 DI 해줍니다.

 

IOC

UserController를 만들고 

namespace App\Http\Controllers;

use App\Repositories\Interfaces\UserRepositoryInterface;

class UserController
{
    private $userRepository;
	
    // 바인딩을 통해 타입힌트로 UserRepositoryInterface를 만나면 
    // 컨테이너에서 UserRepository로 인스턴스화
    public function __construct(UserRepositoryInterface $userRepository)
    {
        $this->userRepository = $userRepository;
    }

    public function index(){
        return response()->json(
            $this->userRepository->getOneById(1)
        );
    }
}

web.php에도 라우팅 추가

Route::get('test', [\App\Http\Controllers\UserController::class, 'index']);

이제 test로 접속했을 때 테이블이 존재하고 id가 1인 값이 있으면 JSON 데이터로 id가 1인 user 데이터를 불러옵니다.

이게 왜 IOC인지는 개발자에 경우 UserRepositoryInterface를 사용하면 프레임워크에서 자동적으로 UserRepository로

의존성을 넣어주기에 제어의 역전인 것입니다.

 

DI, IOC 사용 이점

 

SOLID 원칙을 준수

SOLID 원칙 중 가장 지키기 어려운 DIP, OCP 원칙을 준수하게 됩니다.

 

  • OCP(Open/closed principle) : 개방-폐쇄 원칙
    소프트웨어는 확장은 자유로우나 수정에는 닫혀있어야 한다.
    => Interface의 다형성을 이용해 구현 등으로 확장은 용이하고 수정의 경우 Interface를 따라야 하므로 닫혀있습니다.
  • DIP(Dependency inversion principle) : 의존관계 역전 원칙
    클라이언트는 구현체가 아닌 인터페이스에 의존해야 한다.
    =>  클라이언트 로직에서는 구현체로 변수를 만드는 게 아닌 인터페이스로 변수로 만들기 때문에 개발자는 인스턴스에 신경 쓸 필요가 없습니다.

 

개발했을 때

DI로 의존성 주입부를 모의 객체로 바꾸기 쉽기에 테스트의 편리성을 높이기 좋습니다

아래처럼 DI를 사용하지 않고 Repository를 바로 사용할 경우

class UserController
{
    private $userRepository;

    public function __construct(UserRepository $userRepository)
    {
        $this->userRepository = $userRepository;
    }

    public function index(){
        return response()->json(
            $this->userRepository->getOneById(1)
        );
    }
}

테스트가 필요할 때 기존 UserRepository를 바로 변경하는 리스크를 가지거나

또는 테스트 클래스를 만들고 이를 UserRepository를 사용하는 클라이언트부를 호출하도록 수정하는 일이 발생합니다.

 

하지만 IOC를 적용한 상태라면 클라이언트 코드를 그대로 둔 채

class UserController
{
    private $userRepository;

    public function __construct(UserRepositoryInterface $userRepository)
    {
        $this->userRepository = $userRepository;
    }

    public function index(){
        return response()->json(
            $this->userRepository->getOneById(1)
        );
    }
}

RepositoryProvider만 수정하면 됩니다.

$this->app->singleton(UserRepositoryInterface::class, UserRepository::class);
=>
$this->app->singleton(UserRepositoryInterface::class, NewUserRepository::class);

 

후기

DI/IOC 패턴이 가지는 의도는 알 수 있으나 개발할 때에 있어서는 어려움이 생기기 좋은 것 같습니다.

 

예를 들어 신규 프로젝트를 진행하고 있는 데 중간에 신입 개발자가 들어올 경우 DI/IOC 패턴을 몰라 헤맬 수도 있고

DI, IOC 구조상 클라이언트 부가 아닌 곳에 별도로 정의되어 있기 때문에 이를 알려줄 사람이 부재일 경우 쉽지 않을 것 같습니다.

그리고 오히려 작은 프로젝트에서는 추가해야 하는 코드의 양이 많아지기에 생산성을 떨어트리지 않을까라는 생각도 들었습니다. 

 

결론은  프로젝트를 시작하기 전에 "이 프로젝트는 SOLID를 지켜야 하는지"를 결론 내린 후 적용해야 한다 생각합니다.

 

 

감사합니다.

반응형

'PHP > Laravel' 카테고리의 다른 글

Laravel Routing이 Apache Routing 보다 우선순위가 낮을 때  (0) 2023.06.14
[Laravel9] 라라벨 DB 백업 명령어 만들기 및 자동화 with Artisan console  (0) 2022.11.01
[Laravel9] 라라벨 아티즌 콘솔 명령어 만들기(Artisan console)  (2) 2022.10.31
[Laravel9] 라라벨 테스팅 만들기 with Trait, Factory  (0) 2022.10.28
[Laravel9] 라라벨 이메일 보내기 with Google SMTP, Markdown  (0) 2022.10.24
  1. DI, IOC란
  2. DI
  3. IOC
  4. DI, IOC 사용 이점
  5. 후기
'PHP/Laravel' 카테고리의 다른 글
  • Laravel Routing이 Apache Routing 보다 우선순위가 낮을 때
  • [Laravel9] 라라벨 DB 백업 명령어 만들기 및 자동화 with Artisan console
  • [Laravel9] 라라벨 아티즌 콘솔 명령어 만들기(Artisan console)
  • [Laravel9] 라라벨 테스팅 만들기 with Trait, Factory
DSeung
DSeung
DSeung
Dev log
DSeung
  • 분류 전체보기 (193)
    • PHP (62)
      • Laravel (31)
      • Error (5)
      • Setting (11)
      • Modern PHP (15)
    • Go Lang (51)
      • Study (30)
      • Algorithm (17)
      • Setting (1)
      • Error (3)
    • Java (11)
      • Spring (3)
      • JSP (0)
      • Error (2)
      • Setting (2)
      • 단축키 (2)
    • JavaScript (6)
      • Modern JavaScript (4)
      • Node (1)
    • Android Kotlin (5)
      • Study (4)
      • Error (1)
    • 컴퓨팅 기술 (12)
      • 데이터베이스시스템 (4)
      • Docker (2)
      • 크롤링 & 스크래핑 (1)
      • API (1)
      • 클라우드 (1)
      • 네트워크 (1)
    • MySQL (7)
    • AWS (1)
    • Git (5)
      • GItLab (1)
      • GitHub (4)
    • 도메인 (2)
      • 안과 (2)
    • 자격증 (7)
      • SQLD (1)
      • 정보처리기사 (6)
    • Mac os (1)
    • 나머지 (13)
      • tistory (1)
      • 기타 (9)
      • 일기 (3)
    • 독서 (10)

인기 글

최근 글

블로그 메뉴

  • 홈
  • 태그
전체
오늘
어제
hELLO · Designed By 정상우.v4.2.0
DSeung
[Laravel9] 쉽게 라라벨 DI, IOC 이해하기
상단으로

티스토리툴바

개인정보

  • 티스토리 홈
  • 포럼
  • 로그인

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.