2011-03-10 6 views
2

관계형 DB를 상당히 많이 사용하는 대규모 프로젝트를 진행해 왔습니다. 프로젝트는 C#에 있으며 ORM을 사용하지 않습니다. 응용 프로그램 코드에서 DB에 액세스하는 방식으로 인해 응용 프로그램을 사용하기가 어렵다는 것을 알았지 만 큰 프로젝트에 대한 경험이 충분하지 않아 어떻게 더 좋을지 말할 수는 없었습니다 (아니라고 생각합니다. 방대한 양의 레거시 코드를 변경하지만 다음 프로젝트에서 더 잘 수행하는 방법을 알고 싶습니다.) 귀하의 대답이 C# 또는 ORM 사용과 관련이 있다면 상관 없지만이 문제를 해결하기 위해 취해진 다양한 접근법을 읽고 싶습니다. 여기 응용 프로그램 코드의 DB 액세스 아키텍처

이 프로젝트가 작동하는 방식의 개요입니다 :

  • 꽤 테이블, 뷰, 저장 프로 시저의 작은 소수가 있습니다.
  • DB를에 원시 액세스 (이 층 GetUserById(id), GetUserByLastName(lastName), AddUser(firstname, lastName), GetCommentsByDateAndPostId(date, postId), GetCommentsByDateAndPostIdSortedByDate(date, postId), 등등 ...처럼 보일 것 같은 기능의 무리입니다) 처리 응용 프로그램 코드에서 데이터 액세스 계층이있다. 이 모든 함수는 손으로 작성한 SQL 쿼리를 호출하고 기본적으로 테이블의 메모리 표현을 반환합니다 (즉, results[0].lastName은 행 0의 열 lastName). C#에서는 DataTable입니다.
  • 비즈니스 처리 규칙을위한 데이터 액세스 계층 위에 레이어가 있습니다. 이것은 각 데이터 액세스 함수에 대한 래퍼이지만 해당 (즉, 동일한 이름의) 데이터 액세스 함수를 호출하기 전에 두 가지 비즈니스 로직 검사를 수행 할 수 있습니다. 모든 경우에 데이터 액세스 레이어와 동일한 것을 반환합니다. 응용 프로그램 코드는이 계층을 통해 DB에만 액세스하며 데이터 액세스 계층에는 직접 액세스하지 않습니다.
  • 야생에서 일회성 검색어가 없습니다. 데이터 액세스 계층 (및 비즈니스 논리 계층)의 쿼리와 함수에는 일대일 대응이 있습니다. 데이터베이스가 정규화되었으므로 조인이 필요하므로 대부분의 쿼리에 대한 뷰가 있습니다.
  • 매우 드물게 사용되는 저장 프로 시저가 여기있다 저기

그래서 오늘 새로운 방식으로 DB를 액세스하려면, 내가 DB를 수정해야, 다음 호출하는 데이터 액세스 레이어 기능을 만들 데이터베이스에 사용자 정의 된 SQL 쿼리를 작성한 다음 데이터 액세스 계층 기능을 호출하는 비즈니스 로직 기능을 작성하십시오. 그리고이 변경 사항을 포함 할 수 있도록 기존 함수를 수정하십시오. 나는 불안정한 환경에서 자동화 된 테스트를 어디서 시작해야할지조차 모릅니다.

그리고 DB의 단일 열을 수정하거나 추가하려는 경우입니다. 새 테이블을 추가하려는 경우 선택할 수있는 모든 방법 (WHERE 절의 조합)에 추가하거나 테이블에 삽입하거나 업데이트하거나 삭제하거나 정렬하는 등의 새로운 기능이 추가되었습니다.

+0

이것은 커뮤니티 위키로 표시되어야한다. –

답변

2

당신이 묘사 한 내용은 그 자체로 문제가 아닙니다. 사실 응용 프로그램 디자인 및 패턴 사용의 좋은 예입니다. 문제가있는 것처럼 보이지 않는 것은 유지 보수성을 돕는 새로운 기술/기법을 활용하지 않는다는 것입니다.

예를 들어, 설명을 통해 구조가 기능적 책임을 계층으로 분명히 구분한다는 것은 명백합니다.도메인 (BLL)과 통신하는 프리젠 테이션 (UI)은 리포지토리 패턴을 사용하여 해당 인프라 (DAL)와 통신합니다. 귀하의 BLL은 이미 유효성 확인 및 보안과 같은 교차 관심사를 구현 한 것 같습니다.

이 디자인을 개선하기 위해 수행 할 수있는 작업은 모델을 포함시켜 더 강력한 도메인을 통합하는 것입니다. 이전 ADO.NET DataTable 기술을 삭제하고 데이터베이스를 반영하는 강력한 형식의 모델을 디자인하십시오. ORM을 통합하면 데이터베이스에서 모델을 생성하고 변경 사항을 쉽게 유지할 수있는 힘이 있으므로 엄청난 도움이됩니다.

원하는 ORM 이점을 얻지는 않겠습니다. DAL은 POCO 및 Enumerables를 반환해야합니다. 당신의 BLL이 POCO 데이터, 오류 처리 결과, 유효성 검사 결과 등을 포함 할 수있는 응답 객체를 반환하도록하십시오 (서비스 응답 객체 또는 프리젠 테이션 전송 객체라고 부릅니다).

또 다른 가능한 해결책은 리포지토리 패턴 구현을 일반 리포지토리로 변경하는 것입니다.이 방법은 인프라 논리를 BLL로 유출시킵니다. 예를 들어, 다음 대신에 :

public class UserRepository 
{ 
    public User GetUserById(Int32 userId){...} 
} 

IQueryable을 구현하는 저장소를 만들 수 있습니다 (generics 사용). 이것에 대한 좋은 접근 방법은 nCommon을보십시오. 이렇게하면 다음과 같은 작업을 수행 할 수 있습니다.

var userRepository = new EF4Repository<User>(OrmContextFactory.CreateContext(...)); 
User u = userRepository.Where(user => user.Id == 1).SingleOrDefault(); 

이점은 도메인 비즈니스 로직을 작성하기 만하면된다는 것입니다. 데이터베이스 테이블을 수정해야하는 경우 비즈니스 로직을 한 번만 변경하면됩니다. 그러나이 쿼리는 비즈니스 로직에 존재하며 "리포지토리"를 매체로 사용하여 일부는 부적절하다고 판단되는 데이터베이스와 통신합니다.


당신은 간단한 응답 객체를 생성하기위한 제네릭을 사용할 수 있습니다

UPDATE. 예 :

[DataContract(Name = "ServiceResponseOf{0}")] 
public class ServiceResponse<TDto> : ResponseTransferObjectBase<TDto> where TDto : IDto 
{ 
    #region Constructors 

    /// <summary> 
    /// Initializes a new instance of the <see cref="ServiceResponse&lt;TDto&gt;"/> class. 
    /// </summary> 
    /// <param name="error">The error.</param> 
    /// <remarks></remarks> 
    public ServiceResponse(ServiceErrorBase error) 
     : this(ResponseStatus.Failure, null, new List<ServiceErrorBase> {error}, null) 
    { 
    } 

    /// <summary> 
    /// Initializes a new instance of the <see cref="ServiceResponse&lt;TDto&gt;"/> class. 
    /// </summary> 
    /// <param name="errors">The errors.</param> 
    /// <remarks></remarks> 
    public ServiceResponse(IEnumerable<ServiceErrorBase> errors) 
     : this(ResponseStatus.Failure, null, errors, null) 
    { 
    } 

    /// <summary> 
    /// Initializes a new instance of the <see cref="ServiceResponse&lt;TDto&gt;"/> class with a status of <see cref="ResponseStatus.Failure"/>. 
    /// </summary> 
    /// <param name="validationResults">The validation results.</param> 
    public ServiceResponse(MSValidation.ValidationResults validationResults) 
     : this(ResponseStatus.Failure, null, null, validationResults) 
    { 
    } 

    /// <summary> 
    /// Initializes a new instance of the <see cref="ServiceResponse&lt;TDto&gt;"/> class with a status of <see cref="ResponseStatus.Success"/>. 
    /// </summary> 
    /// <param name="data">The response data.</param> 
    public ServiceResponse(TDto data) 
     : this(ResponseStatus.Success, new List<TDto> { data }, null, null) 
    { 
    } 

    /// <summary> 
    /// Initializes a new instance of the <see cref="ServiceResponse&lt;TDto&gt;"/> class with a status of <see cref="ResponseStatus.Success"/>. 
    /// </summary> 
    /// <param name="data">The response data.</param> 
    public ServiceResponse(IEnumerable<TDto> data) 
     : this(ResponseStatus.Success, data, null, null) 
    { 
    } 

    /// <summary> 
    /// Initializes a new instance of the <see cref="ServiceResponse&lt;TDto&gt;"/> class. 
    /// </summary> 
    /// <param name="responseStatus">The response status.</param> 
    /// <param name="data">The data.</param> 
    /// <param name="errors">The errors.</param> 
    /// <param name="validationResults">The validation results.</param> 
    /// <remarks></remarks> 
    private ServiceResponse(ResponseStatus responseStatus, IEnumerable<TDto> data, IEnumerable<ServiceErrorBase> errors, MSValidation.ValidationResults validationResults) 
    { 
     Status = responseStatus; 
     Data = (data != null) ? new List<TDto>(data) : new List<TDto>(); 

     Errors = Mapper.Map<IEnumerable<ServiceErrorBase>, List<ServiceError>>(errors) ?? 
       new List<ServiceError>(); 

     ValidationResults = 
      Mapper.Map<MSValidation.ValidationResults, List<IValidationResult>>(validationResults) ?? 
      new List<IValidationResult>(); 
    } 

    #endregion 

    #region Properties 

    /// <summary> 
    /// Gets the <see cref="IDto"/> data. 
    /// </summary> 
    [DataMember(Order = 0)] 
    public List<TDto> Data { get; private set; } 

    [DataMember(Order = 1)] 
    public List<ServiceError> Errors { get; private set; } 

    /// <summary> 
    /// Gets the <see cref="ValidationResults"/> validation results. 
    /// </summary> 
    [DataMember(Order = 2)] 
    public List<IValidationResult> ValidationResults { get; private set; } 

    /// <summary> 
    /// Gets the <see cref="ResponseStatus"/> indicating whether the request failed or succeeded. 
    /// </summary> 
    [DataMember(Order = 3)] 
    public ResponseStatus Status { get; private set; } 

    #endregion 
} 

이 클래스는 내 도메인의 결과를 내 서비스 계층 또는 내 프레젠테이션으로 반환하는 데 사용하는 기본 응답 개체입니다. 직렬화가 가능하며 MS Enterprise Library Validation Block을 지원합니다. 유효성 검사를 지원하기 위해 AutoMapper를 사용하여 Microsoft의 유효성 검사 결과를 내 자신의 ValidationResult 객체로 변환합니다. 서비스에서 사용할 때 오류가 발생하기 쉽기 때문에 MS 클래스를 직렬화하려고하지 않는 것이 좋습니다.

오버로드 된 생성자를 사용하면 단일 poco 또는 pocos를 열거 할 수 있습니다. POCOs 대 DataTables ... 항상 강력하게 형식화 된 객체를 사용할 수 있으므로 언제나 좋습니다. T4 템플릿으로 POCO가 자동으로 ORM 모델에서 생성 될 수 있습니다. POCO는 서비스 운영을 위해 DTO에 쉽게 매핑 될 수 있으며 그 반대도 마찬가지입니다. 더 이상 DataTables에 대한 실질적인 필요성이 없습니다. List 대신에 데이터 바인딩과 함께 CRUD 지원을 위해 BindingList를 사용할 수 있습니다.

모든 속성을 채우지 않은 상태로 POCO를 반환하면 문제가 없습니다. Entity Framework에서는이를 프로젝션이라고합니다. 보통 도메인 엔티티가 아닌 사용자 정의 DTO를 만듭니다.


UPDATE

예에 ValidationResult 클래스 : 마이크로 소프트 확인을 변환하는

/// <summary> 
/// Represents results returned from Microsoft Enterprise Library Validation. See <see cref="MSValidation.ValidationResult"/>. 
/// </summary> 
[DataContract] 
public sealed class ValidationResult : IValidationResult 
{ 
    [DataMember(Order = 0)] 
    public String Key { get; private set; } 

    [DataMember(Order = 1)] 
    public String Message { get; private set; } 

    [DataMember(Order = 3)] 
    public List<IValidationResult> NestedValidationResults { get; private set; } 

    [DataMember(Order = 2)] 
    public Type TargetType { get; private set; } 

    public ValidationResult(String key, String message, Type targetType, List<ValidationResult> nestedValidationResults) 
    { 
     Key = key; 
     Message = message; 
     NestedValidationResults = new List<IValidationResult>(nestedValidationResults); 
     TargetType = targetType; 
    } 
} 

예 AutoMapper 코드는에 ValidationResult DTO에 결과 :

Mapper.CreateMap<MSValidation.ValidationResult, IValidationResult>().ConstructUsing(
      dest => 
      new ValidationResult(
       dest.Key, 
       dest.Message, 
       dest.Target.GetType(), 
       dest.NestedValidationResults.Select(mappingManager.Map<MSValidation.ValidationResult, ValidationResult>).ToList())); 
+0

응답 개체가 모두 DAL POCO (예 : 사용자, 댓글 등) 필드 및 오류 결과 등이있는 동일한 클래스 (예 : DatabaseResponse)의 인스턴스 여야합니까? 아니면 응답 유형에 따라 개별 유형의 응답 객체 유형이 있을까요? (아마도 열거 형 vs 단일 객체 또는 가능한 POCO 유형)? –

+0

또한 코드에서 더 예쁘게 보이는 것 외에 DataTable보다 POCO를 사용하면 어떤 이점이 있습니까? 만약 당신이 속성을 모두 채우지 않고 POCO를 반환한다면 ... 어떻게 될까요? –

+0

위 질문에 대한 제 응답을 업데이트했습니다. – Daniel

0

단일 개체 내에서 모든 데이터 액세스 호출을 캡슐화하려면 Facade 패턴을 사용하는 것이 좋습니다. 그런 다음 모든 기존 데이터 액세스 호출을 리팩토링하여 facade 객체를 호출합니다.

Best approach to Architect the integration of two separate databases?에 다른 질문에 대한 응답으로 외관 패턴을 구현하는 방법에 대해 자세히 설명했습니다.

+0

귀하의 게시물을 읽었지만 약간의 설명이 필요합니다. 비즈니스 로직 계층이 기본 데이터베이스 하위 시스템의 외관으로 여기에서 작동하지 않습니까? –

+0

비즈니스 논리처럼 들리며 데이터베이스 논리가 모두 섞여 있습니다. 모든 데이터베이스 호출을 facade로 캡슐화하고 데이터베이스에 대한 모든 비즈니스 오브젝트 호출을 facade 호출로 리 팩터해야합니다. – smartcaveman

관련 문제