Udało się ugotować cebulkę, projekt wygląda znacznie lepiej aniżeli wcześniej. I dodatkowo ma większe możliwości. Stworzyłem też moje pierwsze DDD (Domain-Driven Design), ostatnio zachorowałem w tym kierunku (tak jak CQRS i Onion), i pragnę zgłębiac temat…
Zmiana architektury na tak wczesnym etapie projektu nie była zbyt bolesna. Tym bardziej, iż CQRS został wyodrębniony wcześniej.
Jest to moje pierwsze praktyczne zetknięcie z Clean Architecture (lamer).
PictOgr obecnie składa się z 7 projektów tak jak to widać na pierwszym zrzucie.
Zmiany
Wydzieliłem warstwę domeny w projekcie Core.
Infrastructure zawiera wykorzystane usług, CQRS, implementacje repozytoriuów, Autofac, DTO, AutoMapper, i inne potrzebne elementy, bardziej szczegółowy wykaz na drugim zrzucie.
GUI aplikacji znajdować się będzie w projekcie MVVM, czyli XAMLe, ViewModele oraz moduły dla Autofaca.
Dodałem też projekt, w którym znajdą się testy integracyjne o nazwie E2E.
Zmiana architektury niesie ze sobą kilka zmian dotyczących mechanizmów działania aplikacji!
Użycie eventa do zamykania okien
Na pierwszy ogień, klasa implementująca (IEvent) zdarzenie zamykania aplikacji.
Zdarzenie zamykania aplikacji.
1
2
3
4
5
6
7
8
9
10
11
12
using CQRS.Event;
namespace PictOgr.Infrastructure.Events
{
public class ExitApplicationEvent : IEvent
{
public ExitApplicationEvent(int exitCode)
{
}
}
}
Powyższa klasa jest wykorzystywana przez powiązanego Handlera o nazwie ExitApplicationEventHandler.
Jej celem jest wywołanie przekazanego delegata do zamykania apikacji w metodzie Handle. To spowoduje zamknięcie okienka.
Handler zdarzenia zamykania aplikacji.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using System;
using CQRS.Event;
namespace PictOgr.Infrastructure.Events
{
public class ExitApplicationEventHandler : IEventHandler<ExitApplicationEvent>
{
private readonly Action action;
public ExitApplicationEventHandler(Action action)
{
this.action = action;
}
public void Handle(ExitApplicationEvent @event)
{
action();
}
}
}
Rejestrowanie handlera i przekazanie delegato do zamknięcia okienka.
Rejestrowanie handlera do zdarzenia zamykania aplikacji.
1
2
3
4
eventBus.Register(new ExitApplicationEventHandler(() =>
{
Close();
}));
Pozostaje jedynie w odpowiedniej komendzie ExitApplicationHandler wykonać publikacje zdarzenia ExitApplicationEvent do szyny zdarzeń.
Efektem jest zamknięcie wszystkich okienek w których zarejestrowany jest handler ExitApplicationEventHandler (kod wyżej).
Publikowanie zdarzenia na szynę w handlerze comendy zamykania aplikacji.
1
2
3
4
5
6
public void Handle(ExitApplication command)
{
_logger.Info("Exit application.");
_eventBus.Publish(new ExitApplicationEvent(command.ExitCode));
System.Environment.Exit(command.ExitCode);
}
Wykorzystanie usług w zapytaniach CQRSa
Do przekazywania danych pomiędzy aplikacją, a modelem wykorzystana będzie odrębna klasa określana jako DTO (Data Transfer Object). Dzięki takiemu odseparowaniu aplikacja nic nie wie o modelu domeny jaki zaimplementujemy w aplikacji.
Klasa informacji o aplikacji, DTO - Data Transfer Object, do przekazania informacji od modelu domenowego.
1
2
3
4
5
6
7
namespace PictOgr.Infrastructure.DTO
{
public class ApplicationInformationDto
{
public string Version { get; set; }
}
}
Dane pomiędzy modelem domeny a klasą DTO muszą być przekazane, i można to zrobić pod górkę poprostu przepisaując właściwości z wykorzystaniem klasy pośredniczącej (np. jakiegoś Adaptera), lub zywkorzytaniem biblioteczki AutoMapper.
Poniżej znajduje się konfiguracja AutoMappera dla klas ApplicationInformation <=> ApplicationInformationDto.
Mapowanie obiektu domeny na obiekt DTO.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using AutoMapper;
using PictOgr.Core.Domain;
using PictOgr.Infrastructure.DTO;
namespace PictOgr.Infrastructure.Mappers
{
public static class AutoMapperConfig
{
public static IMapper Initialize() => new MapperConfiguration(config =>
{
config.CreateMap<ApplicationInformation, ApplicationInformationDto>();
config.CreateMap<ApplicationInformationDto, ApplicationInformation>();
}).CreateMapper();
}
}
Konfiguracje ustawiamy dwu kierunkowo oznacza to iż będzie można mapować dane w obu kierunkach:
- ApplicationInformation = ApplicationInformationDto,
- ApplicationInformationDto = ApplicationInformation.
Do pobierania danych z domeny użyjemy tym razem usługi, do jej implementacji wykorzytsamy interfejsik IApplicaitonService, zawierający definicję metody pobierania informacji o aplikacji.
Interfejsik dla usług aplikacji.
1
2
3
4
5
6
7
8
9
using PictOgr.Infrastructure.DTO;
namespace PictOgr.Infrastructure.Services.ApplicationService
{
public interface IApplicationService
{
ApplicationInformationDto GetApplicationInformation();
}
}
Interfejs IApplicaitonService, jest zaimplementowany przez poniższą klasę ApplicationService.
Usługa aplikacji implementująca swój interfejsik.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using AutoMapper;
using PictOgr.Core.Domain;
using PictOgr.Core.Repositories;
using PictOgr.Infrastructure.DTO;
namespace PictOgr.Infrastructure.Services.ApplicationService
{
public class ApplicationService : IApplicationService
{
private readonly IApplicationInformationRepository _applicationInformationRepository;
private readonly IMapper _mapper;
public ApplicationService(
IApplicationInformationRepository applicationInformationRepository,
IMapper mapper)
{
_applicationInformationRepository = applicationInformationRepository;
_mapper = mapper;
}
public ApplicationInformationDto GetApplicationInformation()
{
var applicationInformation = _applicationInformationRepository.GetApplicationInformation();
return _mapper.Map<ApplicationInformation, ApplicationInformationDto>(applicationInformation);
}
}
}
W klasie usługi po pobraniu danych z repozytorium odbywa się mapowanie:
1
return _mapper.Map<ApplicationInformation, ApplicationInformationDto>(applicationInformation);
Efektem jest przeniesienie danych z obiektu domenowego do obiektu DTO.
Użycie modelu domeny do pobrania informacji o aplikacji
Pierwszy obiekt w moim modelu domeny. To klasa z informacjami o aplikacji.
Bardzo banalna, różni się w zasadzie od klasy z nią powiązanej (DTO), wykorzystaniem konstruktora i możliwością ustawienia właściwości Version jedynie właśnie z tego konstruktora (lub metod klasy).
Obiekt domeny zawierający informacje o aplikacji.
1
2
3
4
5
6
7
8
9
10
11
12
namespace PictOgr.Core.Domain
{
public class ApplicationInformation
{
public ApplicationInformation(string version)
{
Version = version;
}
public string Version { get; private set; }
}
}
Jest tutaj też interfejsik z repozytorium pobierania informacji o aplikacji, owe repozytorium będzie implementowane dopiero w warstiwe wyżej (Infrastrukture). Repozytorium jest ściśle związane z modelem ApplicationInformation.
Interfejsik repozytorium pobierania informacji o aplikacji.
1
2
3
4
5
6
7
8
9
using PictOgr.Core.Domain;
namespace PictOgr.Core.Repositories
{
public interface IApplicationInformationRepository
{
ApplicationInformation GetApplicationInformation();
}
}
Jedna metoda pobierania informacji jest implementowana w klasie repozytorium ApplicationInformationRepository, implementuje ona interfejs z modelu domeny IApplicaitonInformationRepository, i dostarcza informacji o aplikacji.
Implementacja repozytorium pobierania danych o aplikacji (warstwa infrastruktury).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using System.Reflection;
using PictOgr.Core.Domain;
using PictOgr.Core.Repositories;
namespace PictOgr.Infrastructure.Repositories
{
public class ApplicationInformationRepository: IApplicationInformationRepository
{
public ApplicationInformation GetApplicationInformation()
{
var version = Assembly.GetExecutingAssembly().GetName().Version;
return new ApplicationInformation($"{version.Major}.{version.Minor}.{version.Build}");
}
}
}
Na chwilę obecną jest to tylko wersja plikacji, jednak z czasem może ulec rozbudowie o więcej ciekawych infomracji. To tyle z dużych zmian w projekcie, myślę iż teraz pujdzie już znacznie lepiej (dla oka = GUI).
Zakończenie
Tak wiem jest to prosty przykład, zapewne wogule nie powinno się robić w ten sposób. Jednak się uczę, i taki przykład dostarcza mi dużo doświadczenia.
Dlatego też postanowiłem zrobić to w ten sposób, być może ktoś dzięki temu wpisowi zrozumie coś wiecej…
Dziękuję za wytrwałość i zachęcam do komentowania.
Jest to post przygotowany na potrzeby konkursu „Daj Się Poznać 2017” organizowanym przez Macieja Aniserowicza.
Blog | https://mrdev.pl |
Projekt | https://mrdev.pl/pictogr-pomysl |
GitHub | github.com/krzysztofowsiany/pictogr |
Snapchat | www.snapchat.com/add/gocom7 |
www.facebook.com/PictOgr-1729700930654225 | |
twitter.com/gemu_gocom | |
RSS | http://mrdev.pl/category/daj-sie-poznac-2017/feed |