Autofixture: Membuat pohon objek tempat anak-anak mereferensikan orang tua untuk desain DDD

Dibuat pada 1 Feb 2014  ·  14Komentar  ·  Sumber: AutoFixture/AutoFixture

Hai, yang di sana.

Saya tidak dapat membuat pohon objek tempat anak-anak mereferensikan orang tua.
Misalnya Mobil memiliki Ban dan referensi Ban mobil itu.

Sejauh ini saya telah menggunakan OmitOnRecursionBehavior dan menetapkan referensi secara manual, demo:

``` c#
var perlengkapan = Perlengkapan baru();
fixture.Behaviors.Add(new OmitOnRecursionBehavior());
var mobil = perlengkapan.Buat();
foreach (ban var di mobil.Ban)
{
ban.Mobil = mobil;
}

What I wanted to do:

``` c#
fixture.Customize<Car>(composer => composer
.With(car => car.Tires, fixture.Build<Tire>().With(tire => tire.Car, car)
.CreateMany().ToList()));

Apa yang saya juga ingin lakukan:

``` c#
Kustomisasi Mobil kelas pribadi: IKustomisasi
{
public void Customize (perlengkapan IFixture)
{
fixture.Behaviors.Add(new OmitOnRecursionBehavior());
fixture.Customizations.Add(New CarBuilder());
}
}

kelas pribadi CarBuilder : ISpecimenBuilder
{
Buat objek publik (permintaan objek, konteks ISpecimenContext)
{
var t = permintaan sebagai Tipe;
if (t != null && t == typeof(Mobil))
{
//Tidak akan berhasil, pembangun bergantung pada dirinya sendiri
var mobil = konteks.Buat();
foreach (ban var di mobil.Ban)
{
ban.Mobil = mobil;
}
}

    return new NoSpecimen(request);
}

}

Here are the Car/Tire classes:

``` c#
public class Car
{
    public string Name { get; set; }
    public string Description { get; set; }
    public int Doors { get; set; }
    public List<Tire> Tires { get; set; }
}

public class Tire
{
    public int Size { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public Car Car { get; set; }
}

Adakah yang bisa membantu saya menyelesaikan ini dengan rapi?

Komentar yang paling membantu

Kode sampel yang disediakan oleh @moodmosaic bersifat idiomatis, sehingga seharusnya menjadi jalan ke depan sebagai solusi spesifik untuk masalah tertentu.

Mengenai kelayakan menggunakan ORM untuk DDD, saya tidak setuju. ORM mungkin tampak kuat, tetapi pada kenyataannya, mereka sangat membatasi. Tampaknya menyelesaikan masalah, tetapi menciptakan lebih banyak masalah.

Ketika datang ke DDD, salah satu pola taktis terpenting yang dijelaskan dalam buku biru adalah konsep _Aggregate Root_. Semua ORM yang pernah saya lihat secara terang-terangan melanggar pola ini, karena Anda dapat memulai dari tabel mana pun dan mulai memuat baris tabel itu, lalu menjalankan berbagai 'properti navigasi' untuk memuat data dari tabel terkait. Ini berarti bahwa tidak ada akar, dan dengan demikian, tidak ada kepemilikan data yang jelas.

Tambahkan ke ini bahwa semuanya bisa berubah, dan Anda akan segera memiliki sistem di tangan Anda, yang sangat sulit untuk dipikirkan.

Semua 14 komentar

Saya pikir Anda akan menemukan bahwa Tire seharusnya tidak memiliki referensi ke induk Car . Referensi melingkar dianggap buruk (setidaknya oleh autofixture).

Namun, ini adalah sesuatu yang juga harus saya selesaikan. Saya bekerja dengan model EF yang tidak dapat saya ubah, dan mereka memiliki referensi melingkar yang tidak dapat saya hapus. Saat ini, saya menghindari perbaikan otomatis, tetapi saya lebih suka berupaya menyelesaikan ini secara umum (jika memungkinkan).

Melakukan Desain Berbasis Domain terkadang nyaman karena sebagian besar logika ada di lapisan domain dan beberapa metode pada anak mungkin memerlukan referensi untuk implementasi yang lebih bersih.

Eric Evans mengatakan dalam bukunya " Desain Berbasis Domain - Mengatasi Kompleksitas di Jantung Perangkat Lunak":
Referensi melingkar secara logis ada di banyak domain dan terkadang diperlukan dalam desain juga, tetapi sulit untuk dipertahankan.

Selain itu, dengan teknologi persistensi populer seperti EF dan Fluent NHibernate, hal ini didorong atau setidaknya lebih mudah untuk pemetaan.

Oleh karena itu, menemukan solusi yang nyaman akan sangat membantu saya dan orang lain yang melakukan DDD, EF, atau Fluent NHibernate.

Saya pikir @ploeh dan @moodmosaic telah menjelaskan di masa lalu, bahwa mereka tidak menyukai referensi melingkar dan menghindarinya sedapat mungkin (koreksi saya jika saya salah, saya tidak bermaksud berbicara untuk Anda, tetapi ini adalah pemahaman saya).

Namun, keduanya diketahui menerima kontribusi ke AutoFixture yang menurut mereka tidak terlalu berguna, asalkan tidak menyebabkan masalah pemeliharaan dan dukungan (sangat adil, menurut saya).

Saya sangat termotivasi untuk mencoba dan melakukan sesuatu tentang masalah ini (dengan asumsi ada sesuatu yang berguna secara umum yang akan berhasil).

Saya suka kerangka kerja yang mendorong desain yang baik dan saya memahami masalah dengan referensi melingkar tetapi kadang-kadang mereka adalah hal yang logis untuk dilakukan. Mungkin dapat ditemukan solusi yang tidak mendorong referensi melingkar tetapi tidak merusak keindahan AutoFixture.

Terima kasih atas jawaban Anda dan sikap positif Anda :+1:

Ya, _jika_ kami berhasil menemukan solusi generik yang layak, itu tidak akan menjadi perilaku _default_ dari AutoFixture.

Dari CarBuilder atas (yang tidak berfungsi), sepertinya yang Anda butuhkan adalah Postprocessor<Car> . Sudahkah Anda mencoba itu?

Secara umum, keinginan untuk menggunakan AutoFixture untuk kelas ORM adalah tema yang berulang, dan seperti yang dicatat oleh @adamchester , saya tidak memiliki masalah untuk membuat ini berhasil jika dapat dilakukan sedemikian rupa sehingga tidak menyebabkan masalah pemeliharaan atau dukungan.

Di sisi lain, tidak menggunakan ORM selama lebih dari tiga tahun, saya tidak memiliki minat pribadi dalam mengejar fitur ini, jadi itu harus bergantung pada orang lain yang memimpin di atasnya.

Mengenai kebutuhan akan referensi melingkar, saya setuju bahwa konsep itu nyata, tetapi bukan berarti ide yang baik untuk menggunakannya dalam desain perangkat lunak. Referensi melingkar akan membuat kode jauh lebih sulit untuk digunakan dan dipikirkan. Jika itu tidak dapat dihindari, maka kita harus hidup dengannya, tetapi IME Anda dapat _selalu_ mendesain API dengan cara menghindari referensi melingkar; Saya tidak tahu kapan terakhir kali saya membuat API dengan referensi melingkar, tetapi sudah bertahun-tahun yang lalu.

Tapi bagaimana dengan ORMS, yang membutuhkan mereka?

Nah, jika Anda menggunakan ORM, Anda tidak lagi bekerja dengan kode berorientasi objek; kode Anda akan _relational_, dengan elemen prosedural yang kuat. Jangan biarkan pilihan bahasa Anda (C#, dll.) menipu Anda.

Mungkin Anda masih dapat melakukan DDD dengan cara ini, tetapi ini adalah DDD relasional, bukan DDD berorientasi objek. Anda akan mendapatkan apa yang disebut Martin Fowler sebagai Skrip Transaksi .

Terima kasih atas jawaban Anda, sangat dihargai :+1:

Saya belum pernah mendengar tentang PostProcessortapi saya mencobanya dan tidak bisa membuatnya bekerja, mungkin saya kehilangan sesuatu. Sepertinya saya menggunakan PostProcessor maka tipenya belum diinisialisasi dengan nilai saat dijalankan.

Ini kode saya:

``` c#
DomainCustomization kelas internal: ICustomization
{
public void Customize (perlengkapan IFixture)
{
fixture.Behaviors.Add(new OmitOnRecursionBehavior());
perlengkapan. Kustomisasi. Tambahkan (
Postprocessor baru(((Perlengkapan)perlengkapan).Mesin, PerintahMobil baru())
);
}
}

kelas internal CarCommand : ISpecimenCommand
{
public void Execute (contoh objek, konteks ISpecimenContext)
{
var mobil = spesimen sebagai Mobil;
jika (mobil != null)
{
//Pengecualian referensi nol di sini, tidak ada ban, tidak ada nilai di sett mobil
foreach (ban var di mobil.Ban)
{
ban.Mobil = mobil;
}
}
}
}
```

Martin Fowler adalah salah satu pahlawan saya. Dalam salah satu postingnya dia berbicara tentang Model Domain Anemia yang persis seperti yang saya coba hindari. Entitas saya memiliki sebagian besar logika dalam aplikasi dan bukan layanan (Scirpt Transaksi). Karena entitas memiliki semua logika, mereka mungkin memerlukan beberapa data yang terkait dengannya.

Saya selalu mencoba untuk mengakhiri dengan menerapkan lapisan kegigihan setelah model domain selesai sehingga database adalah cerminan dari entitas saya bukan sebaliknya.

Namun ORMS seperti EF/EF CodeFirst dan NHibernate membuat Anda membuat hubungan yang tidak diperlukan untuk entitas Anda dan lebih banyak referensi melingkar daripada yang dibutuhkan oleh desain asli Anda sehingga membuat desain Anda relasional.

ORMS ini sangat kuat dan membenarkan "kejelekan" ini.
Saya harus hidup dengan teknologi ini untuk saat ini dan banyak lainnya sehingga solusi untuk masalah ini dapat membantu orang lain.

Berikut ini adalah tes kelulusan di mana SUT dibuat dengan _Tires_ terisi dan setiap Tire mereferensikan Car :

``` c#
[Fakta]
Uji kekosongan publik ()
{
var perlengkapan = Perlengkapan baru();
perlengkapan. Sesuaikan(c => c.Tanpa(x => x.Mobil));
perlengkapan. Kustomisasi. Tambahkan (
FilteringSpecimenBuilder baru (
pascaprosesor baru (
MetodeInvoker baru (
ModestConstructorQuery baru ()),
Pengisi Mobil baru ()),
Spesifikasi Mobil baru()));

var car = fixture.Create<Car>();

Assert.NotEmpty(car.Tires);
Array.ForEach(car.Tires.ToArray(), x => Assert.NotNull(x.Car));

}

Kelas privat CarFiller : ISpecimenCommand
{
public void Execute (contoh objek, konteks ISpecimenContext)
{
if (spesimen == null)
lempar ArgumentNullException baru("spesimen");
jika (konteks == null)
lempar ArgumentNullException baru("konteks");

    var car = specimen as Car;
    if (car == null)
        throw new ArgumentException(
            "The specimen must be an instance of Car.",
            "specimen");

    Array.ForEach(car.GetType().GetProperties(), x =>
    {
        if (x.Name == "Tires")
        {
            var tires =
                ((IEnumerable<object>)context
                    .Resolve(new MultipleRequest(typeof(Tire))))
                        .Cast<Tire>().ToArray();
            Array.ForEach(tires, tire => tire.Car = car);
            x.SetValue(car, tires.ToList());
        }
        else x.SetValue(car, context.Resolve(x.PropertyType));
    });
}

}

Mobil kelas privatSpesifikasi : IRequestSpesifikasi
{
bool publik IsSatisfiedBy (permintaan objek)
{
var requestType = permintaan sebagai Tipe;
jika (jenis permintaan == null)
kembali salah;

    return typeof(Car).IsAssignableFrom(requestType);
}

}
```

Manis! Terima kasih @moodmosaic , saya melewatkan beberapa gambar dari teka-teki ini.
Kode cantik :)

Juga terima kasih kepada @adamchester dan @ploeh atas pemikiran Anda.

Saya akan menutup masalah ini, ini sudah cukup bagi saya. Anda dapat membuka kembali ini jika Anda ingin membuat solusi yang lebih umum.

Kode sampel yang disediakan oleh @moodmosaic bersifat idiomatis, sehingga seharusnya menjadi jalan ke depan sebagai solusi spesifik untuk masalah tertentu.

Mengenai kelayakan menggunakan ORM untuk DDD, saya tidak setuju. ORM mungkin tampak kuat, tetapi pada kenyataannya, mereka sangat membatasi. Tampaknya menyelesaikan masalah, tetapi menciptakan lebih banyak masalah.

Ketika datang ke DDD, salah satu pola taktis terpenting yang dijelaskan dalam buku biru adalah konsep _Aggregate Root_. Semua ORM yang pernah saya lihat secara terang-terangan melanggar pola ini, karena Anda dapat memulai dari tabel mana pun dan mulai memuat baris tabel itu, lalu menjalankan berbagai 'properti navigasi' untuk memuat data dari tabel terkait. Ini berarti bahwa tidak ada akar, dan dengan demikian, tidak ada kepemilikan data yang jelas.

Tambahkan ke ini bahwa semuanya bisa berubah, dan Anda akan segera memiliki sistem di tangan Anda, yang sangat sulit untuk dipikirkan.

Untuk membuat Agregat Akar sejati dengan ORM saat ini tidak mungkin. Agregat Root seharusnya menjadi satu-satunya yang dapat mengubah datanya...

ORM membuat batasan seperti: konstruktor tanpa parameter, setter untuk setiap properti, dan properti navigasi publik. Ini sangat mengganggu saya.

Dengan pendekatan saya saat ini, saya tidak dapat benar-benar mempercayai bahwa pengguna perpustakaan saya mengubah data melalui Agregat Akarnya. Bahkan jika Agregat Akar dapat menerapkan ini, itu tidak akan mencegah pembaruan sql langsung di database ...

Terjebak di SqlServer dan database relasional tidak membantu saya menemukan cara lain. Mungkin saya harus terus mencari cara yang lebih baik.

Meski begitu, saya merasa aplikasi yang saya buat dengan DDD lebih fleksibel untuk diubah dan dipelihara. Sebagian dari itu mungkin juga bantuan TDD dan pengalaman.

Terima kasih atas pemikiran Anda :+1:

Saya perhatikan bahwa jika Anda memiliki panggilan .Customize untuk jenis yang sedang diproses oleh Pengisi yang menyebabkan pengisi diabaikan. misalnya fixture.Customize<Car>(c => c.Without(x => x.Doors))

Saya sudah mencoba menggabungkan ini seperti:

var fixture = new Fixture();
            fixture.Customize<Tire>(c => c.Without(x => x.Car));
            ///fixture.Customize<Car>(c => c.Without(x => x.Doors));
            fixture.Customizations.Add(
                new FilteringSpecimenBuilder(
                    new Postprocessor(
                        SpecimenBuilderNodeFactory.CreateComposer<Car>().WithAutoProperties(!fixture.OmitAutoProperties)
                        .With(x=>x.Description, "Wee")
                        ,
                        new CarFiller()),
                    new CarSpecification()));


            var car = fixture.Create<Car>();

            Assert.Equal("Wee", car.Description);
            Assert.NotEmpty(car.Tires);
            Array.ForEach(car.Tires.ToArray(), x => Assert.NotNull(x.Car));
            Array.ForEach(car.Tires.ToArray(), x => Assert.True(x.Car == car));

Jadi Penegasan pada Deskripsi gagal. Karena masih dibuat secara otomatis, penyesuaian diabaikan. Berdasarkan pemahaman saya yang tegang tentang hal-hal yang saya harapkan spesimen yang diteruskan ke pengisi telah dibuat menggunakan Komposer.

Sunting Sekarang saya melihat bahwa else x.SetValue(car, context.Resolve(x.PropertyType)) secara otomatis menghasilkan properti lainnya. Bersulang

Inilah versi yang lebih umum:

private class CollectionBackReferenceFiller<T, TElement> : ISpecimenCommand
            where T : class
        {
            private readonly string _collectionProperty;
            private readonly Action<TElement, T> _backReferenceSetter;

            public CollectionBackReferenceFiller(string collectionProperty, Action<TElement, T> backReferenceSetter)
            {
                _collectionProperty = collectionProperty;
                _backReferenceSetter = backReferenceSetter;
            }

            public void Execute(object specimen, ISpecimenContext context)
            {
                if (specimen == null)
                    throw new ArgumentNullException("specimen");
                if (context == null)
                    throw new ArgumentNullException("context");

                var typedSpecimen = specimen as T;
                if (typedSpecimen == null)
                    throw new ArgumentException(
                        "The specimen must be an instance of " + typeof(T).Name,
                        "specimen");

                Array.ForEach(typedSpecimen.GetType().GetProperties(), x =>
                {
                    if (x.Name == _collectionProperty)
                    {
                        var elements =
                            ((IEnumerable<object>)context
                                .Resolve(new MultipleRequest(typeof(TElement))))
                                    .Cast<TElement>().ToArray();
                        Array.ForEach(elements, e => _backReferenceSetter(e, typedSpecimen));

                        x.SetValue(typedSpecimen, elements.ToList());
                    }
                });
            }
        }

        private class TypeSpecification<T> : IRequestSpecification
        {
            public bool IsSatisfiedBy(object request)
            {
                var requestType = request as Type;
                if (requestType == null)
                    return false;

                return typeof(T).IsAssignableFrom(requestType);
            }
        }

Solusi generik Anda sangat bagus!
Saya memperbaruinya untuk menggunakan Ekspresi Properti untuk lebih banyak jenis kode aman dan membuat Kustomisasi umum. Saya akhirnya tidak menggunakannya, tetapi saya pikir saya akan membagikannya karena saya sudah menulisnya. :-)

Contoh. CashRegister memiliki Daftar dengan Tanda Terima. Tanda terima memiliki referensi kembali ke CashRegister

public class CashRegister : EntityBase
{
    public List<Receipt> Receipts { get; set; }
}

public class Receipt : EntityBase
{
    public CashRegister CashRegister { get; set; }
}

Penggunaan Kustomisasi:
new BackReferenceCustomization<CashRegister, Receipt>(cashRegister => cashRegister.Receipts, receipt => receipt.CashRegister)

Kustomisasi:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using AutoFixture;
using AutoFixture.Kernel;

public class BackReferenceCustomization<TTypeWithList, TTypeWithBackReference> : ICustomization where TTypeWithList : class
{
    private readonly Expression<Func<TTypeWithList, List<TTypeWithBackReference>>> _collectionPropertyExpression;
    private readonly Expression<Func<TTypeWithBackReference, TTypeWithList>> _backReferencePropertyExpression;

    public BackReferenceCustomization(
        Expression<Func<TTypeWithList, List<TTypeWithBackReference>>> collectionPropertyExpression,
        Expression<Func<TTypeWithBackReference, TTypeWithList>> backReferencePropertyExpression)
    {
        _collectionPropertyExpression = collectionPropertyExpression;
        _backReferencePropertyExpression = backReferencePropertyExpression;
    }

    public void Customize(IFixture fixture)
    {
        fixture.Customize<TTypeWithBackReference>(c => c.Without(_backReferencePropertyExpression));
        fixture.Customizations.Add(
            new FilteringSpecimenBuilder(
                new Postprocessor(
                    new MethodInvoker(new ModestConstructorQuery()),
                    new CollectionBackReferenceFiller<TTypeWithList, TTypeWithBackReference>(_collectionPropertyExpression, _backReferencePropertyExpression)
                ),
                new TypeSpecification<TTypeWithList>()
            )
        );
    }
}

public class CollectionBackReferenceFiller<TTypeWithList, TTypeWithBackReference> : ISpecimenCommand where TTypeWithList : class
{
    private readonly Expression<Func<TTypeWithList, List<TTypeWithBackReference>>> _collectionPropertyExpression;
    private readonly Expression<Func<TTypeWithBackReference, TTypeWithList>> _backReferencePropertyExpression;

    public CollectionBackReferenceFiller(Expression<Func<TTypeWithList, List<TTypeWithBackReference>>> collectionPropertyExpression, Expression<Func<TTypeWithBackReference, TTypeWithList>> backReferencePropertyExpression)
    {
        _collectionPropertyExpression = collectionPropertyExpression;
        _backReferencePropertyExpression = backReferencePropertyExpression;
    }

    public void Execute(object specimen, ISpecimenContext context)
    {
        if (specimen == null)
            throw new ArgumentNullException(nameof(specimen));
        if (context == null)
            throw new ArgumentNullException(nameof(context));

        if (!(specimen is TTypeWithList typedSpecimen))
            throw new ArgumentException(
                "The specimen must be an instance of " + typeof(TTypeWithList).Name,
                nameof(specimen));

        var elements =
            ((IEnumerable<object>)context
                .Resolve(new MultipleRequest(typeof(TTypeWithBackReference))))
            .Cast<TTypeWithBackReference>().ToList();

        var collectionProperty = (PropertyInfo)((MemberExpression)_collectionPropertyExpression.Body).Member;
        collectionProperty.SetValue(typedSpecimen, elements);

        var backReferenceProperty = (PropertyInfo)((MemberExpression)_backReferencePropertyExpression.Body).Member;

        foreach (var element in elements)
        {
            backReferenceProperty.SetValue(element, typedSpecimen);
        }

        var otherProperties = typedSpecimen.GetType().GetProperties().Where(p => !p.Equals(collectionProperty) && p.SetMethod != null);
        foreach (var property in otherProperties)
        {
            property.SetValue(typedSpecimen, context.Resolve(property.PropertyType));
        }
    }
}

public class TypeSpecification<T> : IRequestSpecification
{
    public bool IsSatisfiedBy(object request)
    {
        var requestType = request as Type;
        if (requestType == null)
            return false;

        return typeof(T).IsAssignableFrom(requestType);
    }
}
Apakah halaman ini membantu?
0 / 5 - 0 peringkat

Masalah terkait

Ridermansb picture Ridermansb  ·  4Komentar

ecampidoglio picture ecampidoglio  ·  7Komentar

tiesmaster picture tiesmaster  ·  7Komentar

ploeh picture ploeh  ·  3Komentar

mjfreelancing picture mjfreelancing  ·  4Komentar