Część z nas jest mniej lub bardziej leniwa. Części może przeszkadzać taki zapis, a części nie.
Szczególnie część, gdzie powtarzają się różnego rodzaju serwisy i repozytoria (@7-@14) oraz (@21-@27). Co czynić, jak zrobić to samo za mniej? Konwencje i autoskanowanie assembly’ów. Jak? Poniżej prosta ściągawka: Continue reading
architektura
Posłuchaj klienta
Jeśli klient chce statycznego HTML za kilka minut, nie dostarczaj mu dynamicznego kodu, z pętlami, partialami czy różnymi warstwami za godzinę – za późno.
Jeśli klient chce Abyś wygenerował te notatki w kontrolerze za kilka minut – nie produkuj repozytorium, nie zaprzęgaj dependency injection za godzinę – za późno.
Jeśli klient chce edycji tylko swoich wpisów i możliwości edytowania tylko tytułu czy tekstu, to dostarcz tylko tyle, nie podmieniaj całego modelu – za późno, źle i za dużo.
Pamiętaj, lepiej mieć coś co spełnia wymagania klienta powoli, niż coś co nie spełnia ich szybko.
Posłuchaj swojego klienta, zapytaj jeśli czegoś nie rozumiesz, zaproponuj jeśli uważasz że coś może być zrobione lepiej. Ale pamiętaj, że to jego biznes i to on wie jak robić na tym pieniądze.
Autofac update – dynamiczna zmiana implementacji

1: [HttpGet]
2: public ActionResult Register()
3: {
4: var viewmodel = new RegisterViewModel();
5: return this.View("Register", viewmodel);
6: }
7:
8: [HttpPost]
9: public ActionResult Register(RegisterDto dto)
10: {
11: this.accountService.Register(dto);
12: return this.RedirectToAction("Index");
13: }
1: public void Register(RegisterDto dto)
2: {
3: WebSecurity.CreateUserAndAccount(
4: dto.Email,
5: dto.Password);
6: }
1: public class RegisterDto
2: {
3: public string Email { get; set; }
4: public string Password { get; set; }
5: }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: Consolas, “Courier New”, Courier, Monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #a31515; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
1: [HttpGet]
2: public ActionResult NewUsers()
3: {
4: this.testService.CreateNewUsers();
5: return this.RedirectToAction("Index");
6: }
1: public void CreateNewUsers()
2: {
3: var dto = new RegisterDto
4: {
5: Email = Faker.InternetFaker.Email() + Faker.NumberFaker.Number(),
6: Password = Faker.NumberFaker.Number(1000000000, 2000000000).ToString()
7: };
8: this.accountService.Register(dto);
9: }
1: [HttpGet]
2: [Authorize]
3: public ActionResult NewBlogPost()
4: {
5: var viewmodel = new BlogPostViewModel();
6: return this.View("NewBlogPost", viewmodel);
7: }
8:
9: [Authorize]
10: [HttpPost]
11: public ActionResult NewBlogPost(NewBlogPostDto dto)
12: {
13: this.postingService.NewBlogPost(dto);
14: return this.RedirectToAction("Index");
15: }
1: public void NewBlogPost(NewBlogPostDto dto)
2: {
3: var userId = this.accountService.GetCurrentLoggedUserId();
4: var model = new BlogPost(userId, dto.Text, DateTime.Now);
5: this.database.BlogPosts.Add(model);
6: this.database.SaveChanges();
7: }
1: public class NewBlogPostDto
2: {
3: public string Text { get; set; }
4: }
1: class WebSecurityAccountService : IAccountService
2: {
3: public void Logout()
4: {
5: WebSecurity.Logout();
6: }
7:
8: public bool Login(LoginDto dto)
9: {
10: var result = WebSecurity.Login(dto.Email, dto.Password);
11: return result;
12: }
13:
14: public void Register(RegisterDto dto)
15: {
16: WebSecurity.CreateUserAndAccount(
17: dto.Email,
18: dto.Password);
19: }
20:
21: public int GetCurrentLoggedUserId()
22: {
23: return WebSecurity.CurrentUserId;
24: }
25: }
1: [HttpGet]
2: public ActionResult NewPosts()
3: {
4: this.testService.CreateNewPosts();
5: return this.RedirectToAction("Index");
6: }
1: public void CreateNewPosts()
2: {
3: var users = this.database.Users.ToList();
4: var random = users.ElementAt(Faker.NumberFaker.Number(users.Count - 1));
5: var dto = new NewBlogPostDto
6: {
7: Text = Faker.TextFaker.Sentences(3)
8: };
9:
10: SpecialAccountService.CurrentLoggedUserId = random.Id;
11:
12: this.postingService.NewBlogPost(dto);
13: }
1: public class SpecialAccountService : IAccountService
2: {
3: private readonly WebSecurityAccountService realAccountService;
4:
5: public SpecialAccountService()
6: {
7: this.realAccountService = new WebSecurityAccountService();
8: }
9:
10: public void Logout()
11: {
12: this.realAccountService.Logout();
13: }
14:
15: public bool Login(LoginDto dto)
16: {
17: return this.realAccountService.Login(dto);
18: }
19:
20: public void Register(RegisterDto dto)
21: {
22: this.realAccountService.Register(dto);
23: }
24:
25: static public int CurrentLoggedUserId { get; set; }
26:
27: public int GetCurrentLoggedUserId()
28: {
29: return CurrentLoggedUserId;
30: }
31: }
1: public class HijackService : IHijackService
2: {
3: public void RestoreIOC()
4: {
5: var builder = new ContainerBuilder();
6: builder.RegisterType<WebSecurityAccountService>().As<IAccountService>();
7: builder.Update(ApplicationIocContainer.Container);
8: }
9:
10: public void HijackIOC()
11: {
12: var builder = new ContainerBuilder();
13: builder.RegisterType<SpecialAccountService>().As<IAccountService>().SingleInstance();
14: builder.Update(ApplicationIocContainer.Container);
15: }
16:
17: public IOCStatus GetStatus()
18: {
19: var isHijacked = ApplicationIocContainer.Container.Resolve<IAccountService>() is SpecialAccountService;
20: return isHijacked ? IOCStatus.Hijacked : IOCStatus.Normal;
21: }
22: }
ToBeImplemented reboot

Projekty które robi się dla siebie mają pewna cechę, która jednocześnie jest czymś dobrym i złym. Taki pet-project można rozpoczynać milion razy i nikt nie robi z tego powodu afery – to plus, po milionowym rozpoczęciu szanse na zakończenie są małe – to minus.
Ja właśnie zwiększyłem cyferkę do 3, na szczęście nadal chce mi się to pisać. Rozpocząłem od nowa, bo strasznie pokomplikowałem sobie aplikację, to raz, i przy próbach prostowania doszedłem do wniosku, że chyba szybciej będzie napisać to jeszcze raz, bez takiej ilości magicznego pyłku, który użyłem wcześniej. A dwa, że nauczyłem się paru nowych rzeczy, które chciałem tutaj wykorzystać.
Tym sposobem zmierzam do warstw. O ile wcześniej dzieliłem projekt na proste części:
Jak widać, szaleństwa nie ma. Aplikacja (ASP MVC) korzysta z serwisów, serwisy z bazy oraz czasem z samych siebie (oczywiście przez interfejsy).
Ok, w zasadzie nie ma się czego czepiać, tak/nie? Mogło być lepiej, mogło być gorzej. Zamiast sześciu żółtych prostokątów, mógł być jeden. Zawsze powtarzam, że nie wolno porównywać się gorszym, pozostaje więc tylko czepiać się i szukać miejsca na poprawki.
Co skusiło mnie to tego, żeby to zmienić? W serwisach było w zasadzie wszystko co nie jest związane z bazą danych i z samym mvc, czyli: logika, mapowanie z model na view model, maile, hash, konta użytkowników, blablablabla, wszystko.
Czy to dobre? It depends. Jeśli robi się mały prosty projekt (taki książkowy), to może będzie to dobre rozwiązanie, jeśli robi się nowego fejsbuka to raczej nie, a przypominam, że już nie długo moja aplikacja zajmie jego miejsce – widzę i słyszę wasz śmiech, zobaczymy kto będzie się śmiać ostatni!
Co takiego pozmieniałem w swoim rozwiązaniu? Warstwy, więcej warstw, znacie to przysłowie, że każdy problem da się rozwiązań dodając kolejną warstwę abstrakcji?
Razem z tym zmieniłem także troszkę podejście do rozwiązywania zależności, które tworzę, wygląda mniej więcej tak:
Największe zmiany to wiele aplikacji które są wspierane, robię to żeby sprawdzić jak długo można utrzymywać wspólny kod, na razie bez cross-platform.
Idealnie jest aby aplikacje były maksymalnie lekkie, cała logika jest przeniesiona do części Bussines, która wykonuje właściwą pracę. W przypadku aplikacji WPF są jej dwa warianty, jeden oparty o Application.REST, drugi który referuje bezpośrednio do Bussines i działa “natywnie”.
Dodatkowo ważny jest brak zależności w poziomie, tzn. serwisy nie mogą zależeć od serwisów, infrastruktura od infrastruktury, czy biznes od biznesu. Poszczególne klasy w danej warstwach muszą być samodzielne, jeśli ma się pojawiać jakaś zależność, oznacza to że klasa chce wziąć na klatę zbyt dużą odpowiedzialność i że jest źle.
Prawdę mówiąc, jestem dopiero na początku tej drogi i wiem, że pojawią się bloczki (już są), które będą specyficzne dla poszczególnych aplikacji, ale jestem na to gotowy i na aktualnym stopniu mojej znajomości naszej programistycznej domeny jestem w stanie to zaakceptować.
TL;DR:
W projektach gdzie zależności są poziome, np. serwisA zależy od serwisuB są, złe. Dwa powody: może to prowadzić to zależności cyklicznych, oraz oznaczać to może, że jeden z nich ma za dużą odpowiedzialność, ponieważ część pracy przekazuje do sąsiada o którym nie powinien wiedzieć.
Słowo motywujące na dziś: nie bój się eksperymentować!
Ciekawy moich postępów czy rozwiązań, aktualny projekt możesz znaleźć tutaj:
https://jstadnicki.visualstudio.com/DefaultCollection/_git/ToBeImplemented
https://github.com/jstadnicki/tbi