Co do problemu z implementacją to widzę dwa wyjścia:
- Usunąć
Bar<T>
- no bo niby czemu to ma służyć, skoro i tak piszesz metody specyficzne dla obsługiwanych przez siebie typów kontrolerów.
To musi pozostać dla wszystkich innych kontrolerów nieobsługiwanych przez dany plan. To wygląda mniej więcej tak:
class BasicPlanGuard: PlanGuard
{
protected override bool CheckPlan(ActionExecutingContext context, ControllerActionDescriptor descriptor)
{
return CheckController((dynamic)context.Controller, descriptor);
}
bool CheckController(ClientsController c, ControllerActionDescriptor descriptor)
{
//sprawdza np., czy użytkownik może dodać kolejnego klienta
}
bool CheckController(PremiseController c, ControllerActionDescriptor descriptor)
{
//sprawdza np., czy użytkownik może dodać kolejny lokal
}
bool CheckController<T>(T c, ControllerActionDescriptor descriptor)
{
return true; //tu idą wszystkie inne kontrolery, które nie mają żadnych ograniczeń dla danego planu
}
}
- Usunąć
(object obj)
, wszystkie pozostałe metody nazwać Foo
i pozwolić działać normalnemu przeciążaniu metod bez kombinowania.
No właśnie nie da się tak, co opisałem kodem powyżej :)
Ale moim zdaniem główny problem tutaj to ogólna koncepcja:
- Czemu jeden filtr ma sprawdzać różne reguły biznesowe dla różnych kontrolerów? Każda weryfikowana akcja powinna mieć swój kontroler. Wiązanie każdego
PlanGuarda
z każdym kontrolerem to ścisłe wiązanie, które skutkuje trudnościami w zmienianiu i rozwijaniu takiego kodu. Dodając nowy kontroler będziesz musiał zmienić wszystkie guardy. Dodając nowegoguarda będziesz musiał obsłużyć w nim wszystkie kontrolery. Brzmi groźnie. I łamie SRP i OCP.
No właśnie nie. Dodając nowy kontroler, nie muszę zmieniać nic w Guardach ze względu na tą generyczną wersję metody CheckController. Dlatego też nie wiążę PlanGuarda z każdym kontrolerem, tylko PlanGuard obsługuje tylko te kontrolery, które mają mieć jakieś ograniczenie. Akcje dla konkretnych kontrolerów są sprawdzane w metodzie CheckController w taki sposób:
bool CheckController(ClientsController c, ControllerActionDescriptor descriptor)
{
if(descriptor.ActionName == nameof(c.Post))
{
//oznacza, że użytkownik próbuje dodać nowego klienta, więc sprawdzam tu, czy może zgodnie ze swoim planem
}
}
Jeśli dodam nowy plan kiedyś, to muszę obsłużyć w nim tylko konkretne akcje dla konkretnych kontrolerów.
- Czy te wszystkie
PlanGuardy
są na pewno potrzebne? Jeśli poszczególne plany różnią się jedynie liczbami operacji danego rodzaju możliwymi do wykonania przez użytkownika, to przecież wystarczy umieścić te limity w jakichś claimsach zalogowanego użytkownika, a potem jeden "Guard' może po prostu porównać dany limit z obecnym wykorzystaniem i zezwolić na wykonanie akcji bądź nie.
Jest to jakieś rozwiązanie, ale moim zdaniem zbyt ubogie. Moje rozwiązanie było czasowo porównywalne kosztowo do Twojego. Natomiast umożliwia mi dużo więcej. Być może za miesiąc jakiś plan w ogóle nie będzie mógł wykorzystać jakiejś akcji albo będzie musiał zareagować w specyficzny sposób. To wszystko mógłbym oczywiście zaszyć w claimsach, ale rozwiązanie z guardami wydaje mi się czystsze. Claimsy w pewnym momencie mogłyby narobić trochę burdelu w kodzie, a tymi guardami mogę zrobić wszystko. Być może to trochę na wyrost, ale mechanizm jest stosunkowo prosty i kosztowo porównywalny :)
Swoją drogą jestem ciekawy, jak to się robi w innych aplikacjach. Nie potrafię teraz oszacować ilości requestów na sekundę. Mam nadzieję, że opcja z rzutowaniem na dynamic się sprawdzi. Jeśli nie, będzie trzeba wymyślić coś innego. Może wtedy faktycznie pokombinuję z Claimsami.