Usunięcie zależności między klasami

0

Ostatnio postanowiłem napisać aplikację bez stosowania CQRS na handlerach. Okazało się jednak, że moje klasy odpowiadające za dokonywanie operacji zapisu do bazy: CustomerStore i CartStore są od siebie zależne. W moim przypadku chcę, aby po uzupełnieniu danych klienta został utworzony dla niego koszyk. Oznacza to, że CustomerStore potrzebuje referencji do CartStore. Tylko że jeśli zacznę wiązać tego typu klasy referencjami, to szybko dostanę jakieś circular reference. Poza tym mam wrażenie, że te klasy po prostu powinny być od siebie niezależne. Jak sobie z tym poradzić?

public class CustomerStore
    {
        // ...

        public async Task<Result> CompleteAsync(CompleteCustomerModel model)
        {
            var customer = await dbContext.Customers.FindAsync(model.CustomerId);

            if (customer == null)
                return new Result(ErrorName.NotValid, "Customer does not exist.");

            customer.FirstName = model.FirstName;
            customer.LastName = model.LastName;
            customer.Address = model.Address;
            customer.CompletedAt = DateTime.Now;

            await dbContext.SaveChangesAsync();
            // TODO Create cart
            return Result.SuccessfulResult;
        }
public class CartStore
    {
        // ...

        public async Task<Result> CreateAsync(string customerId)
        {
            dbContext.Carts.Add(new Cart { CartId = customerId });
            await dbContext.SaveChangesAsync();
            return Result.SuccessfulResult;
        }
    }
1

Dobra bo nie ma co w komentarzu sie klocic.

MSDN Says

If a group of objects contain references to each other, but none of these object are referenced directly or indirectly from stack or shared variables, then garbage collection will automatically reclaim the memory.

wiecej tutaj: https://stackoverflow.com/questions/8840567/garbage-collector-and-circular-reference
lub tu https://stackoverflow.com/questions/400706/circular-references-cause-memory-leak

0

Powiedzmy, że mam taki kod:

public class A
{
    B b;

    public A(B b)
    {
        this.b = b;
    }
}

public class B
{
    A a;

    public B(A a)
    {
        this.a = a;
    }
}

W module Autofaca:

builder.RegisterType<A>().InstancePerRequest();
builder.RegisterType<B>().InstancePerRequest();

Po odpaleniu dostaję błąd: Circular component dependency detected. Raczej w tym tkwi mój problem ;)

W dokumentacji jest napisane: Two types with circular constructor dependencies are not supported. You will get an exception when you try to resolve types registered in this manner.

1

screenshot-20190321101535.png

Ok teraz wiem o co biega ci i z czym problem. Generalnie troche kiepsko klasy zaprojektwoales ze w takiej formie sa potrzebe tu i tam. I zwarcam honor zapomniale ze konsturkotrami mozna sobie ubic appke ;) raz tak zrobilem i pozniej nigdy tego bledu nie powielilem. Tu masz grafike w jaki sposob to rozwiazac. W skrocie np tworzysz klase pomocnicza i z tamtych te konstruktowy wyrzucic. Albo przepisac kod bo ewiedentie robisz cos zle :(

public class C
{
    B b;
    A a;
    public C(A a,B b)
    {
        this.b = b;
        this.a = a;
    }
}
0

Czyli mam stworzyć jakąś klasę CompleteCustomerHandler/Processor i do niej wrzucić tę logikę? Hmmm, chciałem napisać aplikację bez stosowania CQRS na handlerach a tu okazuje się, że się nie da przy choć trochę bardziej skomplikowanej aplikacji...

Co prawda tej cyklicznej referencji jeszcze nie mam, ale pewnie prędzej czy później się jakaś pojawi...

0

chciałem napisać aplikację bez stosowania CQRS na handlerach

Co przez to rozumiesz?

0

Bez stosowania command-handler i query-handler. Po prostu: jedna klasa do zapisu i jedna do odczytu, ale efekty są takie jakie są...

0

Czasem nie jest tak że na siłę chcesz uniknąć stosowania czegoś, co właśnie pozwoli Ci rozwiązać Twój problem? ;)

0

;) Może, ale chcę zobaczyć, że po prostu da się napisać dobrą aplikację bez handlerów. @somekind @neves Taki podział widziałem u Was. Jak byście rozwiązali ten problem?

1

Podstawową zasadą projektowania jest tworzenie zależności jedno kierunkowych. Robienie takich fasad jest trochę bez sensu. Lepiej by było wykonać metodę np. "initiateCard" na DM i go zapisać do Repozytorium.

1

Jej, referencje w obie kierunki nie są problem w przypadku obiektów domenowych, ORM sobie z tym poradzi. Aczkolwiek tak jak już było wspomniane chcemy mieć jak najmniej takich referencji, najlepiej jakby graf zależności przypominał drzewko bez cykli.

CustomerStore nie potrzebuje referencji do CartStore, (te *Story to takie odpowiedniki Repo jak się domyślam), orkiestrację robisz warstwę wyżej w serwisie aplikacyjnym lub kontrolerze.

CustomerService.CreateCustomer()
{
     var userId = CustomerStore.CreateCustomer();  
     CartStore.CreateCart(userId);
}

a bardziej obiektowo to by wyglądało tak:

CustomerService.CreateCustomer(UnitOfWork uow)
{
    Customer customer = Customer.Create();     
    uow.CustomerRepo.Add(customer);
    uow.Save();
}

class Customer()
{

    public Create()
    {
          Cart = Cart.Create();
    }
}

.
.
.

Realny przykład z mojego pet projectu, dodanie pytania pytania razem z odpowiedziami do katalogu, interfejs repozytorium wygląda tak:

    internal interface IQuestionsCatalogRepository
    {       
        QuestionsCatalog GetById(long id, bool includeQuestions = false);     
    }

tak, zawiera tylko jeden nagłówek metody,

internal sealed class QuestionsService : IQuestionsService
{
    (...)

        public Result<long> CreateQuestionWithAnswers(long ownerId, CreateQuestion createQuestion)
        {
            var catalog = uow.QuestionsCatalogs.GetById(createQuestion.CatalogId.Value);

            if (catalog == null)
            {
                return Result.Error(Errors.CatalogNotFound);
            }

            if (catalog.OwnerId != ownerId)
            {
                return Result.Unauthorized();
            }

            var question = Question.Create(createQuestion.Content, ownerId);

            if (createQuestion.Answers != null)
            {
                foreach (var answer in createQuestion.Answers)
                {
                    question.AddAnswer(answer.Content, answer.IsCorrect);
                }
            }

            catalog.AddQuestion(question);

            uow.Save();

            return Result.Ok(question.QuestionId);
        }
    (...)
}
internal sealed class Question
{
     private readonly List<Answer> _answers = new List<Answer>();

        public static Question Create(string content,  long ownerId)
        {
            return new Question() {Content = content,  OwnerId = ownerId };
        }


         
        public Answer AddAnswer(string content, bool isCorrect)
        {
            var answer = Answer.Create(content, isCorrect);           
            _answers.Add(answer);
            return answer;
        }

}
1

No ja przede wszystkim uważam za złe robienie referencji między klasami z tej samej warstwy, bo efektem tego jest po prostu spaghetti.

No i nie bardzo rozumiem, czemu CustomerStore ma mieć referencje do CartStore. Skoro tworzenie koszyka jest nieodłącznym elementem uzupełnienia klienta, to co tu wydzielać?

public class CustomerStore
    {
        // ...

        public async Task<Result> CompleteAsync(CompleteCustomerModel model)
        {
            var customer = await dbContext.Customers.FindAsync(model.CustomerId);

            if (customer == null)
                return new Result(ErrorName.NotValid, "Customer does not exist.");

            customer.FirstName = model.FirstName;
            customer.LastName = model.LastName;
            customer.Address = model.Address;
            customer.CompletedAt = DateTime.Now;
            dbContext.Carts.Add(new Cart { CartId = customerId });

            await dbContext.SaveChangesAsync();
            // TODO Create cart
            return Result.SuccessfulResult;
        } 
0
neves napisał(a):

Jej, referencje w obie kierunki nie są problem w przypadku obiektów domenowych, ORM sobie z tym poradzi. Aczkolwiek tak jak już było wspomniane chcemy mieć jak najmniej takich referencji, najlepiej jakby graf zależności przypominał drzewko bez cykli.

CustomerStore nie potrzebuje referencji do CartStore, (te *Story to takie odpowiedniki Repo jak się domyślam), orkiestrację robisz warstwę wyżej w serwisie aplikacyjnym lub kontrolerze.

CustomerService.CreateCustomer()
{
     var userId = CustomerStore.CreateCustomer();  
     CartStore.CreateCart(userId);
}

a bardziej obiektowo to by wyglądało tak:

CustomerService.CreateCustomer(UnitOfWork uow)
{
    Customer customer = Customer.Create();     
    uow.CustomerRepo.Add(customer);
    uow.Save();
}

class Customer()
{

    public Create()
    {
          Cart = Cart.Create();
    }
}

.
.
.

Realny przykład z mojego pet projectu, dodanie pytania pytania razem z odpowiedziami do katalogu, interfejs repozytorium wygląda tak:

    internal interface IQuestionsCatalogRepository
    {       
        QuestionsCatalog GetById(long id, bool includeQuestions = false);     
    }

tak, zawiera tylko jeden nagłówek metody,

internal sealed class QuestionsService : IQuestionsService
{
    (...)

        public Result<long> CreateQuestionWithAnswers(long ownerId, CreateQuestion createQuestion)
        {
            var catalog = uow.QuestionsCatalogs.GetById(createQuestion.CatalogId.Value);

            if (catalog == null)
            {
                return Result.Error(Errors.CatalogNotFound);
            }

            if (catalog.OwnerId != ownerId)
            {
                return Result.Unauthorized();
            }

            var question = Question.Create(createQuestion.Content, ownerId);

            if (createQuestion.Answers != null)
            {
                foreach (var answer in createQuestion.Answers)
                {
                    question.AddAnswer(answer.Content, answer.IsCorrect);
                }
            }

            catalog.AddQuestion(question);

            uow.Save();

            return Result.Ok(question.QuestionId);
        }
    (...)
}
internal sealed class Question
{
     private readonly List<Answer> _answers = new List<Answer>();

        public static Question Create(string content,  long ownerId)
        {
            return new Question() {Content = content,  OwnerId = ownerId };
        }


         
        public Answer AddAnswer(string content, bool isCorrect)
        {
            var answer = Answer.Create(content, isCorrect);           
            _answers.Add(answer);
            return answer;
        }

}

Dlaczego nie użyjesz po prostu słowa new zamiast metody create.?
Dlaczego tworzysz repozytoria za pomocą UOW.?

1
Gworys napisał(a):

Dlaczego nie użyjesz po prostu słowa new zamiast metody create.?

Cytując Effective Java, Chapter 2, Item 1

- One advantage of static factory methods is that, unlike constructors, they have names." -- you can't have two different constructors that take an int argument, but you can have two static factory methods called createWithAge(int age) and createWithHeight(int height)
- A second advantage of static factory methods is that, unlike constructors, they are not required to create a new object each time they’re invoked.
- A third advantage of static factory methods is that, unlike constructors, they can return an object of any subtype of their return type.
Gworys napisał(a):

Dlaczego tworzysz repozytoria za pomocą UOW.?

Bo tak jest zdecydowanie wygodniej i bardziej jawnie niż gdyby uow miałby być wstrzykiwany do repozytorium.

0
neves napisał(a):
Gworys napisał(a):

Dlaczego nie użyjesz po prostu słowa new zamiast metody create.?

Cytując Effective Java, Chapter 2, Item 1

- One advantage of static factory methods is that, unlike constructors, they have names." -- you can't have two different constructors that take an int argument, but you can have two static factory methods called createWithAge(int age) and createWithHeight(int height)
- A second advantage of static factory methods is that, unlike constructors, they are not required to create a new object each time they’re invoked.
- A third advantage of static factory methods is that, unlike constructors, they can return an object of any subtype of their return type.
Gworys napisał(a):

Dlaczego tworzysz repozytoria za pomocą UOW.?

Bo tak jest zdecydowanie wygodniej i bardziej jawnie niż gdyby uow miałby być wstrzykiwany do repozytorium.

Ciekawe zwłaszcza pierwszy punkt do mnie przemawia. Jednak wolę trzymać się swojej konwencji i w ogóle nie używać słowa "create" (tam gdzie się da).

Pamiętam, że Fowler pisał o "UOW Cotroller" co zdaje się to przypominać, lecz osobiście nie podoba mi się taka implementacja. Wiadomo, kto co woli. :P

1 użytkowników online, w tym zalogowanych: 0, gości: 1