.NET 세계에서 데이터베이스에 액세스하는 데 가장 많이 사용되는 방법 중 하나는 언어 구문과 긴밀하게 통합된 ORM(객체 관계형 매퍼)인 EF(Entity Framework)를 사용하는 것입니다. .NET 언어에 기본으로 제공되는 LINQ(Language Integrated Queries)를 사용하면 SQL에 대한 지식이 없어도 일반 .NET 컬렉션을 사용하여 작업하는 것처럼 데이터에 액세스할 수 있습니다. 여기에는 장점과 단점이 있으므로 여기서는 언급하지 않으려고 합니다. 그러나 지속적으로 발생하는 문제 중 하나는 소프트웨어 프로젝트의 구조, 추상화 수준 및 궁극적으로 단위 테스트와 관련된 혼란입니다.
이 게시물에서는 저장소 추상화가 항상 유용한 이유를 설명하려고 합니다. 많은 사람들이 추상화된 데이터 액세스에 대한 용어로 저장소를 사용하는 반면, 유사한 것과 관련된 저장소 소프트웨어 패턴도 있지만 동일한 것은 아닙니다. 여기에서는 저장소를 데이터 액세스의 구현 세부 사항을 추상화하는 일련의 인터페이스라고 부르고 디자인 패턴을 완전히 무시하겠습니다.
알고 계시다면 건너뛰셔도 됩니다. 하지만 먼저 저장소에 대한 아이디어를 어떻게 얻게 되었는지부터 설명해야 합니다.
선사시대에는 코드가 구조 없이 모든 내용을 담고 있는 그대로 작성되었으며, 원하는 대로 또는 적어도 바라는 대로 수행되었습니다. 자동화된 테스트는 없었고 작동할 때까지 수동으로 해킹하고 테스트했습니다. 각 애플리케이션은 코드 구조, 재사용 또는 가독성보다 하드웨어 요구 사항이 더 중요하다는 점을 고려하여 현재 사용 가능한 모든 방식으로 작성되었습니다. 그게 공룡을 죽인 이유야! 사실입니다.
천천히 패턴이 나타나기 시작했습니다. 특히 비즈니스 애플리케이션의 경우 비즈니스 코드, 데이터 지속성 및 사용자 인터페이스가 명백히 분리되었습니다. 이를 레이어라고 부르며 곧 서로 다른 프로젝트로 분리되었습니다. 이는 서로 다른 관심사를 다루었을 뿐만 아니라 이를 구축하는 데 필요한 기술이 특히 달랐기 때문입니다. UI 디자인은 코드 로직 작업과 매우 다르며 SQL이나 데이터를 유지하는 데 사용된 언어나 시스템과도 매우 다릅니다.
따라서 비즈니스와 데이터 계층 간의 상호작용은 인터페이스와 모델로 추상화하여 이루어졌습니다. 비즈니스 클래스에서는 테이블의 항목 목록을 요청하지 않고 필터링된 복잡한 개체 목록이 필요합니다. 지속되는 모든 것에 액세스하고 이를 비즈니스가 이해할 수 있는 항목에 매핑하는 것은 데이터 계층의 책임입니다. 이러한 추상화는 저장소라고 불리기 시작했습니다.
데이터 액세스의 하위 계층에서는 CRUD와 같은 패턴이 빠르게 자리를 잡았습니다. 테이블과 같은 구조화된 지속성 컨테이너를 정의하고 레코드를 생성, 읽기, 업데이트 또는 삭제했습니다. 코드에서 이러한 종류의 논리는 목록, 사전 또는 배열과 같은 컬렉션으로 추상화됩니다. 따라서 리포지토리는 컬렉션처럼 작동해야 하며 실제 생성, 읽기, 업데이트 및 삭제 외에는 다른 방법이 없을 만큼 충분히 일반적이어야 한다는 의견도 있었습니다.
그러나 저는 이에 동의하지 않습니다. 비즈니스로부터의 데이터 액세스 추상화로서, 비즈니스 요구 사항을 기반으로 모델링하는 대신 데이터 액세스 패턴에서 최대한 멀리 떨어져야 합니다. 특히 Entity Framework의 사고방식과 다른 많은 ORM이 저장소의 원래 아이디어와 충돌하기 시작하여 EF에서 저장소를 절대 사용하지 말라는 요청으로 끝나고 이를 안티패턴이라고 부르는 지점이 있습니다.
모델 간의 부모-자식 관계로 인해 많은 혼란이 발생합니다. 사람이 포함된 부서 엔터티와 같습니다. 부서 저장소는 사람이 포함된 모델을 반환해야 합니까? 어쩌면 그렇지 않을 수도 있습니다. 그렇다면 저장소를 부서(사람 제외)와 사람으로 분리한 다음 비즈니스 모델에 매핑할 별도의 추상화를 갖는 것은 어떨까요?
비즈니스 계층을 하위 계층으로 분리하면 실제로 혼란이 커집니다. 예를 들어, 대부분의 사람들이 비즈니스 서비스라고 부르는 것은 특정 비즈니스 논리를 특정 유형의 비즈니스 모델에만 적용하는 추상화입니다. 앱이 사람과 함께 작동하므로 Person이라는 모델이 있다고 가정해 보겠습니다. 사람을 처리하는 클래스는 PeopleRepository를 통해 지속성 계층에서 비즈니스 모델을 가져오는 PeopleService가 될 것이지만 데이터 모델과 비즈니스 모델 간의 매핑 또는 사람과 관련된 특정 작업(예: 계산)을 포함하여 다른 작업도 수행합니다. 급여. 그러나 대부분의 비즈니스 로직은 여러 유형의 모델을 사용하므로 서비스는 추가 책임 없이 저장소에 래퍼를 매핑하게 됩니다.
이제 EF를 사용하여 데이터에 액세스한다고 상상해 보세요. SQL 테이블에 매핑하는 엔터티 컬렉션을 포함하는 DbContext 클래스를 이미 선언해야 합니다. LINQ를 사용하면 이를 반복, 필터링 및 매핑할 수 있으며, 이는 백그라운드에서 효율적으로 SQL 명령으로 변환되어 계층적 부모-자식 구조로 완성된 필요한 정보를 제공합니다. 이러한 변환은 특정 열거형이나 이상한 데이터 구조와 같은 내부 비즈니스 데이터 유형의 매핑도 처리합니다. 그렇다면 리포지토리나 서비스가 왜 필요한가요?
더 많은 추상화 계층이 무의미한 오버헤드처럼 보일 수 있지만 프로젝트에 대한 인간의 이해를 높이고 변화의 속도와 품질을 향상한다고 믿습니다. 분명히 균형이 있습니다. 모든 소프트웨어 디자인 패턴이 모든 곳에서 사용되어야 한다는 명백한 요구 사항으로 설계된 시스템을 본 적이 있습니다. 추상화는 코드 가독성과 문제 분리를 향상시키는 경우에만 유용합니다.
EF가 번거로워지는 상황 중 하나는 단위 테스트입니다. DbContext는 많은 노력을 들여 수동으로 모의해야 하는 종속성이 많은 복잡한 시스템입니다. 따라서 Microsoft는 메모리 데이터베이스 공급자라는 아이디어를 내놓았습니다. 따라서 무엇이든 테스트하려면 메모리 데이터베이스를 사용하고 작업을 완료하면 됩니다.
Microsoft 페이지에서는 이 테스트 방법이 이제 "권장되지 않음"으로 표시됩니다. 또한 이러한 예에서도 EF는 리포지토리에 의해 추상화됩니다.
메모리 데이터베이스 테스트가 작동하는 동안 해결하기 쉽지 않은 몇 가지 문제가 추가됩니다.
따라서 결국 사람들은 "도우미" 메서드 내에서 데이터베이스의 모든 것을 설정한 다음 이 불가해하고 복잡한 메서드로 시작하여 가장 작은 기능까지 테스트하는 테스트를 생성하게 됩니다. EF 코드가 포함된 모든 코드는 이 설정 없이는 테스트할 수 없습니다.
따라서 리포지토리를 사용하는 한 가지 이유는 테스트 추상화를 DbContext 위로 이동하는 것입니다. 이제 데이터베이스가 전혀 필요하지 않고 저장소 모의만 필요합니다. 그런 다음 실제 데이터베이스를 사용하여 통합 테스트에서 저장소 자체를 테스트하십시오. 인메모리 데이터베이스는 실제 데이터베이스와 매우 유사하지만 조금 다릅니다.
실생활에서 실제 가치가 거의 없다고 인정하는 또 다른 이유는 데이터에 액세스하는 방식을 변경하고 싶을 수도 있다는 것입니다. 어쩌면 NoSql이나 일부 메모리 분산 캐시 시스템으로 변경하고 싶을 수도 있습니다. 또는 모놀리식 데이터베이스와 같은 데이터베이스 구조로 시작하여 이제 이를 다양한 테이블 구조를 가진 여러 데이터베이스로 리팩터링하려고 할 가능성이 훨씬 높습니다. 리포지토리 없이는 불가능하다는 점을 즉시 말씀드리겠습니다.
그리고 Entity Framework의 경우, 얻는 엔터티는 데이터베이스에 매핑된 활성 레코드입니다. 하나를 변경하고 다른 항목의 변경 사항을 저장하면 갑자기 첫 번째 엔터티도 DB에서 업데이트됩니다. 아니면 내용을 포함하지 않았거나 맥락이 바뀌어서 포함하지 않았을 수도 있습니다.
EF 지지자들은 항상 엔터티 추적을 매우 긍정적인 것으로 과장합니다. 데이터베이스에서 엔터티를 가져와 비즈니스를 수행한 다음 엔터티를 업데이트하고 저장한다고 가정해 보겠습니다. 리포지토리를 사용하면 데이터를 얻은 다음 비즈니스를 수행한 다음 약간의 업데이트를 수행하기 위해 데이터를 다시 가져옵니다. EF는 이를 메모리에 유지하고 변경 전에 업데이트되지 않았다는 것을 알고 있으므로 두 번 읽지 않습니다. 그것은 사실이다. 이는 데이터베이스 변경 사항을 어떻게든 인식하고 데이터베이스에서 처리하는 모든 것을 추적하는 데이터베이스용 메모리 캐시를 설명합니다. 달리 지시하지 않는 한 데이터베이스 항목을 복잡한 C# 엔터티에 양방향으로 매핑하고 변경 사항을 앞뒤로 추적하는 동시에 깊이 내장되어 있습니다. 비즈니스 코드에서. 개인적으로 나는 이러한 과도한 책임과 우려사항 분리의 부족이 이를 사용하여 얻은 어떤 성과보다 훨씬 더 해롭다고 생각합니다. 게다가, 약간의 초기 노력을 통해 비즈니스, 캐싱 및 데이터 액세스 간의 명확한 경계를 유지하면서 모든 기능을 저장소 또는 저장소에 대한 또 다른 메모리 캐싱 계층에서 추상화할 수 있습니다.
사실 이 모든 것에서 실제로 어려운 점은 별도의 관심사를 가져야 하는 시스템 간의 경계를 결정하는 것입니다. 예를 들어, 필터링 논리를 데이터베이스의 저장 프로시저로 이동하면 많은 성능을 얻을 수 있지만, 사용된 알고리즘의 테스트 가능성과 가독성이 손실됩니다. 반대로 EF 또는 다른 메커니즘을 사용하여 모든 논리를 코드로 이동하는 것은 성능이 떨어지고 때로는 실행 불가능합니다. 아니면 데이터 개체가 비즈니스 개체가 되는 지점은 어디입니까(위의 부서 및 사람 예 참조)?
아마도 가장 좋은 전략은 먼저 이러한 경계를 정의한 다음 어떤 기술과 디자인이 거기에 적합할지 결정하는 것입니다.
저는 저장소가 Entity Framework 또는 기타 ORM을 사용하더라도 서비스 및 저장소 추상화를 항상 사용해야 한다고 믿습니다. 그것은 모두 우려 사항의 분리로 귀결됩니다. 나는 Entity Framework를 유용한 소프트웨어 추상화라고 생각하지 않습니다. 왜냐하면 너무 많은 짐이 있기 때문입니다. 따라서 코드에서 이를 추상화하는 데 저장소가 많이 사용됩니다. EF는 유용한 추상화이지만 소프트웨어가 아닌 데이터베이스 액세스용입니다.
내 소프트웨어 작성 철학은 애플리케이션 요구 사항부터 시작하여 해당 요구 사항에 맞는 구성 요소를 만들고 인터페이스를 통해 하위 수준 기능을 추상화하는 것입니다. 그런 다음 다음 수준에서 프로세스를 반복하여 항상 코드를 읽을 수 있는지 확인하고 현재 수준에서 사용되는 구성 요소나 사용되는 구성 요소를 이해할 필요가 없는지 확인합니다. 그렇지 않다면 우려 사항을 잘못 분리한 것입니다. 따라서 비즈니스 애플리케이션에는 특정 데이터베이스나 ORM을 사용해야 하는 요구 사항이 없었으므로 데이터 계층 추상화는 이에 대한 모든 지식을 숨겨야 합니다.
기업이 원하는 것은 무엇입니까? 필터링된 사람들 목록이요? var people = service.GetFilteredListOfPeople(filter); 그 이상도 그 이하도 아닙니다. 서비스 메소드는 return mapPeople(repo.GetFilteredListOfPeople(mappedFilter)); 다시 말하지만 그 이상도 그 이하도 아닙니다. 저장소가 어떻게 사람들을 구하고, 사람들을 구하고, 다른 일을 하는지는 서비스의 관심사가 아닙니다. 캐싱을 원하는 경우 IPeopleRepository를 구현하고 IPeopleRepository에 대한 종속성을 갖는 일부 캐싱 메커니즘을 구현합니다. 매핑을 원하면 올바른 IMapper 인터페이스를 구현하십시오. 등등.
이 기사에서는 지나치게 장황한 내용을 다루지 않았으면 좋겠습니다. 이는 소프트웨어 문제가 아니라 개념적 문제에 가깝기 때문에 특별히 코드 예제를 제외했습니다. Entity Framework가 여기에서 제가 불만을 제기하는 대부분의 대상일 수 있지만 이는 작은 일에서는 마술처럼 도움이 되지만 중요한 일을 망가뜨리는 모든 시스템에 적용됩니다.
도움이 되었기를 바랍니다!
위 내용은 ORM을 사용해도 리포지토리를 사용하여 코드로 데이터 액세스의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!