programing

C# MongoDB: 도메인 개체를 올바르게 매핑하는 방법은 무엇입니까?

bestprogram 2023. 6. 26. 21:31

C# MongoDB: 도메인 개체를 올바르게 매핑하는 방법은 무엇입니까?

저는 최근에 Evans의 Domain-Driven 디자인 책을 읽기 시작했고 DDD에 대한 경험을 쌓기 위해 작은 샘플 프로젝트를 시작했습니다.동시에 MongoDB에 대해 더 알고 싶었고 SQL EF4 저장소를 MongoDB 및 최신 공식 C# 드라이버로 교체하기 시작했습니다.이제 이 질문은 MongoDB 매핑에 관한 것입니다.저는 공공 게터와 세터로 간단한 물체를 매핑하는 것이 꽤 쉽다는 것을 압니다. - 거기에는 고통이 없습니다.하지만 공용 설정자가 없는 도메인 엔티티를 매핑하는 데 어려움이 있습니다.제가 배운 바와 같이, 유효한 엔티티를 구성하는 유일한 정말 깨끗한 접근법은 필요한 매개 변수를 생성자에게 전달하는 것입니다.다음 예를 생각해 보십시오.

public class Transport : IEntity<Transport>
{
    private readonly TransportID transportID;
    private readonly PersonCapacity personCapacity;

    public Transport(TransportID transportID,PersonCapacity personCapacity)
    {
        Validate.NotNull(personCapacity, "personCapacity is required");
        Validate.NotNull(transportID, "transportID is required");

        this.transportID = transportID;
        this.personCapacity = personCapacity;
    }

    public virtual PersonCapacity PersonCapacity
    {
        get { return personCapacity; }
    }

    public virtual TransportID TransportID
    {
        get { return transportID; }
    } 
}


public class TransportID:IValueObject<TransportID>
{
    private readonly string number;

    #region Constr

    public TransportID(string number)
    {
        Validate.NotNull(number);

        this.number = number;
    }

    #endregion

    public string IdString
    {
        get { return number; }
    }
}

 public class PersonCapacity:IValueObject<PersonCapacity>
{
    private readonly int numberOfSeats;

    #region Constr

    public PersonCapacity(int numberOfSeats)
    {
        Validate.NotNull(numberOfSeats);

        this.numberOfSeats = numberOfSeats;
    }

    #endregion

    public int NumberOfSeats
    {
        get { return numberOfSeats; }
    }
}

분명히 자동 매핑은 여기서 작동하지 않습니다.이제 3개의 클래스를 손으로 매핑할 수 있습니다.BsonClassMaps그리고 그것들은 잘 보관될 겁니다.할 때 것입니다.BsonDocuments그리고 그것들을 내 도메인 객체로 구문 분석합니다.저는 많은 노력을 했지만 결국 깨끗한 해결책을 얻지 못했습니다.MongoDB용 퍼블릭 게터/세터로 DTO를 생성하고 도메인 개체에 매핑해야 합니까?누군가 이것에 대해 조언을 해줄 수 있을지도 모릅니다.

속성이 읽기 전용인 클래스를 직렬화/직렬화할 수 있습니다.도메인 개체 저항을 무지하게 유지하려는 경우 BsonAttributes를 사용하여 직렬화를 안내하고 싶지 않을 것이며, AutoMapping에는 읽기/쓰기 속성이 필요하므로 클래스 맵을 직접 등록해야 합니다.예를 들어, 클래스는 다음과 같습니다.

public class C {
    private ObjectId id;
    private int x;

    public C(ObjectId id, int x) {
        this.id = id;
        this.x = x;
    }

    public ObjectId Id { get { return id; } }
    public int X { get { return x; } }
}

다음 초기화 코드를 사용하여 매핑할 수 있습니다.

BsonClassMap.RegisterClassMap<C>(cm => {
    cm.MapIdField("id");
    cm.MapField("x");
});

개인 필드는 읽기 전용일 수 없습니다.또한 역직렬화는 생성자를 무시하고 개인 필드를 직접 초기화합니다(.NET 직렬화도 이러한 방식으로 작동합니다).

다음은 이를 테스트하는 전체 샘플 프로그램입니다.

http://www.pastie.org/1822994

저는 BSON 문서를 파싱하고 파싱 로직을 공장으로 이동할 것입니다.

먼저 빌더 클래스를 포함하는 공장 기본 클래스를 정의합니다.Builder 클래스는 DTO 역할을 하지만 도메인 개체를 구성하기 전에 값을 추가로 검증합니다.

public class TransportFactory<TSource>
{
    public Transport Create(TSource source)
    {
        return Create(source, new TransportBuilder());
    }

    protected abstract Transport Create(TSource source, TransportBuilder builder);

    protected class TransportBuilder
    {
        private TransportId transportId;
        private PersonCapacity personCapacity;

        internal TransportBuilder()
        {
        }

        public TransportBuilder WithTransportId(TransportId value)
        {
            this.transportId = value;

            return this;
        }

        public TransportBuilder WithPersonCapacity(PersonCapacity value)
        {
            this.personCapacity = value;

            return this;
        }

        public Transport Build()
        {
            // TODO: Validate the builder's fields before constructing.

            return new Transport(this.transportId, this.personCapacity);
        }
    }
}

이제 저장소에 공장 하위 클래스를 만듭니다.이 공장에서는 BSON 문서에서 도메인 개체를 구성합니다.

public class TransportRepository
{
    public Transport GetMostPopularTransport()
    {
        // Query MongoDB for the BSON document.
        BsonDocument transportDocument = mongo.Query(...);

        return TransportFactory.Instance.Create(transportDocument);
    }

    private class TransportFactory : TransportFactory<BsonDocument>
    {
        public static readonly TransportFactory Instance = new TransportFactory();

        protected override Transport Create(BsonDocument source, TransportBuilder builder)
        {
            return builder
                .WithTransportId(new TransportId(source.GetString("transportId")))
                .WithPersonCapacity(new PersonCapacity(source.GetInt("personCapacity")))
                .Build();
        }
    }
}

이 접근 방식의 이점은 다음과 같습니다.

  • 작성자는 도메인 개체를 작성할 책임이 있습니다.이렇게 하면 특히 도메인 개체가 공용 생성자를 노출하지 않는 경우 일부 사소한 유효성 검사를 도메인 개체 밖으로 이동할 수 있습니다.
  • 공장에서 원본 데이터를 구문 분석합니다.
  • 도메인 개체는 비즈니스 규칙에 초점을 맞출 수 있습니다.구문 분석이나 사소한 검증에 문제가 되지 않습니다.
  • 추상 팩토리 클래스는 필요한 각 원본 데이터 유형에 대해 구현할 수 있는 일반 계약을 정의합니다.예를 들어 XML을 반환하는 웹 서비스와 인터페이스해야 하는 경우 새 공장 하위 클래스를 만듭니다.

    public class TransportWebServiceWrapper
    {
        private class TransportFactory : TransportFactory<XDocument>
        {
            protected override Transport Create(XDocument source, TransportBuilder builder)
            {
                // Construct domain object from XML.
            }
        }
    }
    
  • 원본 데이터의 구문 분석 논리는 데이터가 발생하는 위치와 비슷합니다. 즉, BSON 문서의 구문 분석은 저장소에 있고 XML의 구문 분석은 웹 서비스 래퍼에 있습니다.이렇게 하면 관련 논리가 함께 그룹화됩니다.

몇 가지 단점:

  • 저는 아직 규모가 크고 복잡한 프로젝트에서는 이러한 접근법을 시도하지 않았습니다. 단지 소규모 프로젝트에서만요.제가 아직 마주치지 못한 몇몇 시나리오에서 약간의 어려움이 있을 수 있습니다.
  • 단순해 보이는 것에 대한 코드입니다.특히 건축업자들은 상당히 크게 성장할 수 있습니다.모든 코드를 변환하여 빌드의 코드 양을 줄일 수 있습니다.WithXxx()메서드에서 단순 속성으로 이동합니다.

현재 이 문제를 처리하는 더 나은 방법은 다음과 같습니다.MapCreator(이 답변의 대부분이 작성된 후 추가되었을 수 있음).

예를 들어 저는 라는 수업이 있습니다.Time세 가지 읽기 전용 속성을 사용합니다.Hour,Minute그리고.Second데이터베이스에 이 세 가지 값을 저장하고 새 값을 구성하는 방법은 다음과 같습니다.Time역직렬화 중인 개체입니다.

BsonClassMap.RegisterClassMap<Time>(cm =>
{
    cm.AutoMap();
    cm.MapCreator(p => new Time(p.Hour, p.Minute, p.Second));
    cm.MapProperty(p => p.Hour);
    cm.MapProperty(p => p.Minute);
    cm.MapProperty(p => p.Second);
}

닐스는 흥미로운 솔루션을 가지고 있지만, 저는 데이터 모델을 단순화하는 훨씬 다른 접근 방식을 제안합니다.

당신이 RDBMS 스타일 엔티티를 MongoDB로 변환하려고 시도하고 있으며, 당신이 발견한 것처럼 매핑이 잘 되지 않기 때문에 제가 이렇게 말하는 것입니다.

NoSQL 솔루션을 사용할 때 고려해야 할 가장 중요한 사항 중 하나는 데이터 모델입니다.SQL 및 관계에 대해 알고 있는 대부분의 정보에서 벗어나 임베디드 문서에 대해 더 많이 생각해야 합니다.

그리고 기억하세요, MongoDB는 모든 문제에 대한 정답이 아닙니다. 따라서 강제로 정답이 되지 않도록 노력하세요.다음 예제는 표준 SQL 서버에서 잘 작동할 수 있지만 MongoDB에서 작동하는 방법을 찾으려다가 자살하지는 마십시오. 그렇지 않을 수도 있습니다.대신 MongoDB로 예제 데이터를 모델링하는 올바른 방법을 찾는 것이 좋은 연습이 될 것이라고 생각합니다.

C#의 MongoDB용 오픈 소스 ORM인 NoRM을 생각해 보십시오.

다음은 몇 가지 링크입니다.

http://www.codevoyeur.com/Articles/20/A-NoRM-MongoDB-Repository-Base-Class.aspx

http://lukencode.com/2010/07/09/getting-started-with-mongodb-and-norm/

https://github.com/atheken/NoRM (계속)

언급URL : https://stackoverflow.com/questions/5744430/c-sharp-mongodb-how-to-correctly-map-a-domain-object