Moja implementacja DDD w C#

Moja implementacja DDD w C#
maszrum
  • Rejestracja:około 5 lat
  • Ostatnio:12 miesięcy
  • Lokalizacja:Kraków
  • Postów:219
3

Chciałbym zaprezentować efekt mojej miesięcznej pracy, a mianowicie aplikację systemu głosowania. Jej tematyka może i nie jest zbyt pomysłowa. Jednak zamysł na aplikację wziął się stąd, że chciałem w końcu zaimplementować to słynne DDD "tak jak się należy". Tak, aby mieć szablon na jakieś przyszłe projekty. Po przejrzeniu setek repozytoriów zabrałem się za pracę. Po napisaniu dużej części kodu zorientowałem się, że duża jego część (która jest potrzebna) nie wygląda zbyt ciekawie - różnego rodzaju interfejsy, obsługa CQRS, rejestracje w Autofacu i tym podobne rzeczy. Cały ten kod wyniosłem do osobnych bibliotek, które później nazwałem SharpDomain. Chciałbym usłyszeć opinię o moim projekcie oraz czy takie rozwiązanie ma w ogóle sens (wydzielenie biblioteki DDD pod przyszłe projekty). Czy wszystko zrobiłem zgodnie ze sztuką?

W projekcie wykorzystałem:

  • do IoC: Autofac,
  • do CQRS: MediatR,
  • do walidacji: FluentValidation,
  • do mapowania: Automapper,
  • do persystencji: własne rozwiązanie "in memory" (czyli właściwie jest tylko symulacja persystencji)
    Aplikacja posiada uwierzytelnianie i autoryzację użytkowników (bez hasła, ale to celowo).

SharpDomain - znajduje się tu kod który mi nie pasował w aplikacji docelowej i go wyniosłem do osobnych bibliotek.

SharpDomain.Example - repozytorium zawierające system głosowania. Są tam dwie aplikacje: konsolowa oraz WebApi. Nie mają one ze sobą żadnej interakcji. Są dwie dlatego, że chciałem aplikację odpalić też w postaci konsolowej, a nie tylko w ASP.NET Core. Aplikacja API nie posiada frontendu - tylko Swagger (być może ktoś jest chętny na dodanie?). Aplikacja konsolowa posiada symulowanych głosujących, dlatego liczba głosów może się samoistnie zmieniać.

Kod nie posiada testów - jestem tego w pełni świadomy. Wynika to z tego, że nie poruszam się jeszcze w tym temacie swobodnie. Kod zmieniał się intensywnie i często - nie chciałem tracić czasu na dostosowywanie testów. Z dokumentacją sytuacja wygląda podobnie - nie chciałem ciągle gonić za często zmieniającym się kodem.

Jako ciekawostkę podam, że wrzuciłem te biblioteki na Nugeta i w ciągu miesiąca mam ponad 3000 pobrań. Nie wiem z czego to wynika, wątpię by ktoś ich używał bez jakiejkolwiek dokumentacji. Może jakieś roboty?

Nie wiem czy struktura katalogów jaką wybrałem jest odpowiednia. Mam tu na myśli to, że katalogi są ponazywane na przykład: Commands, Queries, Models. Czy może lepiej pogrupować je według "domeny", na przykład Vote, Question i dopiero tam wszystko co związane z danym modelem?

Czekam na waszą opinię. Jak ktoś stwierdzi, że nie takie słabe to ucieszę się z gwiazdki na Githubie.

XardasLord
  • Rejestracja:ponad 11 lat
  • Ostatnio:9 miesięcy
  • Lokalizacja:Gdańsk
  • Postów:271
1

Mam pytanie, z jakiegoś konkretnego powodu zdecydowałeś się na Autofac, zamiast skorzystać z wbudowanego DI w .NET Core?

Nie wiem czy struktura katalogów jaką wybrałem jest odpowiednia. Mam tu na myśli to, że katalogi są ponazywane na przykład: Commands, Queries, Models. Czy może lepiej pogrupować je według "domeny", na przykład Vote, Question i dopiero tam wszystko co związane z danym modelem?

Ja grupuję pierw agregaty, a następnie dzielił je na dane usecase w jego obrębie i wydzielał Commands/Queries, jako że w DDD wszystko opiera się o domenę, to w większości przypadków też z takim podziałem się najczęściej w warstwie aplikacyjnej spotykałem w przeróżnych artykułach i przykładach.

PS. Postaram się w późniejszym czasie przejrzeć kod i do niego odnieść.


maszrum
  • Rejestracja:około 5 lat
  • Ostatnio:12 miesięcy
  • Lokalizacja:Kraków
  • Postów:219
1
XardasLord napisał(a):

Mam pytanie, z jakiegoś konkretnego powodu zdecydowałeś się na Autofac, zamiast skorzystać z wbudowanego DI w .NET Core?

Nie potrafię w jakiś logiczny sposób odpowiedzieć na to pytanie. Być może dlatego, że lepiej znam Autofaca, bardziej podoba mi się jego API? Posiada dość prostą integrację z DI .Net Core'a.

Ja grupuję pierw agregaty, a następnie dzielił je na dane usecase w jego obrębie i wydzielał Commands/Queries, jako że w DDD wszystko opiera się o domenę, to w większości przypadków też z takim podziałem się najczęściej w warstwie aplikacyjnej spotykałem w przeróżnych artykułach i przykładach.

Też tak myślę. Przy mało skomplikowanej domenie moje dotychczasowe rozwiązanie może i ma swoje plusy, ale na dłuższą metę wydaje mi się, że się nie sprawdzi. Zmienię to w wolnej chwili.

PS. Postaram się w późniejszym czasie przejrzeć kod i do niego odnieść.

Czekam, liczę na każdą opinię. Próbuję znaleźć najlepsze praktyki. Widzę, że sam na swoim GH też masz taki szablon. Chętnie przejrzę, obcego kodu nigdy za wiele :) .

maszrum
  • Rejestracja:około 5 lat
  • Ostatnio:12 miesięcy
  • Lokalizacja:Kraków
  • Postów:219
0

Aktualizacja zmian:

  • Dodałem kilka testów integracyjnych (czy to są testy integracyjne? niech ktoś mnie poprawi jeśli nie).

  • Przerobiłem strukturę katalogów na zgodną z domeną, czyli to o czym pisaliśmy wcześniej w tym wątku. Miałem jeden problem, z którego rozwiązania nie jestem zbyt zadowolony. Podczas tworzenia nowych przestrzeni nazw dla tych katalogów, na przykład: VotingSystem.Application.Question zrobił mi się konflikt nazw z klasą modelu Question. Zmieniłem nazwy wszystkich modeli na ...Model, na przykład QuestionModel. Może ktoś ma jakąś podpowiedź jak to zrobić lepiej, bo jednak wolałbym uniknąć tego ...Model

somekind
Moderator
  • Rejestracja:około 17 lat
  • Ostatnio:minuta
  • Lokalizacja:Wrocław
1
maszrum napisał(a):

Jak dla mnie, to nie, bo nie uruchamiasz ich na żywej aplikacji, tylko uderzasz w MediatR bezpośrednio, na dodatek w testach składasz IoC na nowo.
Czyli testy nie wykryją Ci problemu z błędną konfiguracją IoC w produkcyjnym kodzie, ani problemów na poziomie weba/HTTP.

Może ktoś ma jakąś podpowiedź jak to zrobić lepiej, bo jednak wolałbym uniknąć tego ...Model

Użyć liczby mnogiej w namespacach?

maszrum
  • Rejestracja:około 5 lat
  • Ostatnio:12 miesięcy
  • Lokalizacja:Kraków
  • Postów:219
0
somekind napisał(a):

Użyć liczby mnogiej w namespacach?

Wstyd przyznać, ale... nie wpadłem na to, dzięki.

Jak dla mnie, to nie, bo nie uruchamiasz ich na żywej aplikacji, tylko uderzasz w MediatR bezpośrednio, na dodatek w testach składasz IoC na nowo.
Czyli testy nie wykryją Ci problemu z błędną konfiguracją IoC w produkcyjnym kodzie, ani problemów na poziomie weba/HTTP.

Czyli rozumiem, że testy integracyjne powinno robić się w postaci zapytań HTTP i weryfikacji odpowiedzi? Coś na zasadzie "czarnej skrzynki", ale czy wtedy nie są to testy end-to-end? Muszę się doszkolić z tego tematu. Postanowiłem, że skoro napisałem te moje nieszczęsne testy pseudo-integracyjne to nie będę ich już usuwał. Zastanawiam się tylko, czy nie nazwać ich inaczej.

na dodatek w testach składasz IoC na nowo.

Rozwiązałem ten problem wydzielając budowanie IoC do osobnego projektu i teraz korzystają z niego: aplikacja konsolowa, web API i testy.

@somekind: A ogólnie jak ocenisz kod? Generalnie to wydaje mi się, że to jest chyba najbardziej przeinżynierowana aplikacja jaką kiedykolwiek widziałem. Choć z drugiej strony taki był zamysł, bo ma to być szablon pod jakieś przyszłe projekty oraz moja edukacja.

edytowany 1x, ostatnio: maszrum
somekind
Moderator
  • Rejestracja:około 17 lat
  • Ostatnio:minuta
  • Lokalizacja:Wrocław
1
maszrum napisał(a):

Wstyd przyznać, ale... nie wpadłem na to, dzięki.

Oj tam, jakbyś frontend robił, to byś się mógł wstydzić. ;)

Czyli rozumiem, że testy integracyjne powinno robić się w postaci zapytań HTTP i weryfikacji odpowiedzi? Coś na zasadzie "czarnej skrzynki", ale czy wtedy nie są to testy end-to-end? Muszę się doszkolić z tego tematu. Postanowiłem, że skoro napisałem te moje nieszczęsne testy pseudo-integracyjne to nie będę ich już usuwał. Zastanawiam się tylko, czy nie nazwać ich inaczej.

No ja testami integracyjnymi w przypadku API nazywam testy end-to-end, i chyba nie jestem w tym odosobniony.

Rozwiązałem ten problem wydzielając budowanie IoC do osobnego projektu i teraz korzystają z niego: aplikacja konsolowa, web API i testy.

No to ma sens.

@somekind: A ogólnie jak ocenisz kod? Generalnie to wydaje mi się, że to jest chyba najbardziej przeinżynierowana aplikacja jaką kiedykolwiek widziałem. Choć z drugiej strony taki był zamysł, bo ma to być szablon pod jakieś przyszłe projekty oraz moja edukacja.

Ogólnie, to ja nie chcę oceniać, bo ja nie do końca rozumiem ideę. Wygląda w sumie jak framework bardziej do CQS i opakowanie na MediatR niż DDD, albo ja po prostu nie widzę granicy między jednym a drugim w tym projekcie, albo czegoś jest za mało.
Ale na pewno warto pisać sobie framework, bo można się przy tym wiele nauczyć, np. jeśli w pewnym momencie postanowimy nie przywiązywać się do jednego kontenera IoC, lecz umożliwić integrację z różnymi.

maszrum
  • Rejestracja:około 5 lat
  • Ostatnio:12 miesięcy
  • Lokalizacja:Kraków
  • Postów:219
0
somekind napisał(a):

Ogólnie, to ja nie chcę oceniać, bo ja nie do końca rozumiem ideę. Wygląda w sumie jak framework bardziej do CQS i opakowanie na MediatR niż DDD, albo ja po prostu nie widzę granicy między jednym a drugim w tym projekcie, albo czegoś jest za mało.

Tak właściwie to ja sam nie wiem co to ma być. Biblioteka, framework, czy jakiś zbiór najlepszych zasad zgromadzonych przeze mnie w jednym miejscu. Idea była taka, że chciałem napisać jakąś prostą aplikację w DDD, ale tak idealnie, książkowo. W trakcie pracy zauważyłem dużo "brudnego" kodu, który zaciemniał całą logikę, a który według mnie jest uniwersalny i równie dobrze sprawowałby się w innych projektach (a przynajmniej mi się tak wydaje). I wtedy wydzieliłem to wszystko do osobnego repozytorium. W żadnym razie nie było to od początku planowane jako jakiś framework.

bardziej do CQS

Właśnie nie bardzo rozumiem różnicę pomiędzy CQS i CQRS. Oczywiście mogę poczytać regułki z książek, ale jakby nie mogę znaleźć różnicy w kodzie. Czy CQS to jakby "idea", a CQRS to jej implementacja? Coś jak Scrum i Agile?

Ale na pewno warto pisać sobie framework, bo można się przy tym wiele nauczyć, np. jeśli w pewnym momencie postanowimy nie przywiązywać się do jednego kontenera IoC, lecz umożliwić integrację z różnymi.

To chyba trzeba by zrobić jakąś warstwę abstrakcji nad IoC, jego builderem i tak dalej? Ja się tego nie podejmę na pewno. Podoba mi się mój Autofac :P.

Aktualizacja: Dapper + PostgreSQL

Dodałem obsługę bazy danych PostgreSQL przez Dappera. Napotkałem przy tym kilka trudności, ale na szczęście udało mi się je pokonać (lub mam plan je pokonać :P):

  • Problem z istniejącymi testami

Kiedy pisałem testy integracyjne miałem "bazę danych", czyli po prostu atrapę in-memory. Wszystkie przechodziły na zielono, do czasu aż skończyłem prawdziwą persystencję. Wtedy okazało się, że testy nie przechodzą, bo były dostosowane do ulotnej pamięci, to znaczy: każdy test odpalał aplikację na nowo, przez co pracował na "świeżej" instancji.

Rozwiązałem to dodając mechanizm inicjalizacji aplikacji. Do wyboru są dwa warianty: IfNeed - tworzy tabele jeśli aplikacja wykryje, że jakiś brakuje i Forcefully - kasuje wszystkie tabele i tworzy je na nowo. W normalnym użyciu wykorzystuje pierwszą opcję, w testach drugą. Trudności były z zachowaniem OCP, przy wdrażaniu tego, ale wydaje mi się, że poszło ok.

  • Konfiguracja i przechowywanie ConnectionString

Tego jeszcze nie zrobiłem, ale mniej więcej mam plan jak to zrobić. Na razie trzymam konfigurację bazy danych "zahardkodowaną". Planuję po prostu wykorzystać konfigurację od ASP.NET Core, wpleść ją jakoś do mojego systemu i ConnectionString trzymać w pliku konfiguracyjnym. Nie wiem, czy to dobre rozwiązanie?

  • Zmiany w warstwie aplikacji

Nie było żadnych zmian. Udało mi się doprowadzić do takiego rozwiązania, że wystarczy zakomentować jedną linię #define i odkomentować inną w tym pliku. Nie musiałem zmieniać ani jednej linijki w warstwie aplikacji, żeby przejść z jednego sposobu persystencji na drugi. Miałem wydzielony interfejsy read repozytoriów, więc poszło gładko. A write repozytoria komunikują się z warstwą aplikacji przez zdarzenia.

Dygresja

Może to jest banalne, ale ja spędziłem nad tym kawałek czasu, zanim ogarnąłem o co chodzi.
Kiedy pisałem jakiś fragment kodu (mniejsza już jaki), chciałem wywołać metodę obiektu z jakieś Nugetowej biblioteki. Wiedziałem, że na pewno ta metoda istnieje, ale w żadnej sposób IntelliSense nie chciał mi jej podpowiedzieć, a kompilator wywalał błędy. Wtedy po przeszukaniu Stackoverflow odkryłem, że można implementować metody interfejsów w klasie sposobem implicit. Na prawdę nie wiedziałem, że można ukryć publiczną metodę interfejsu w klasie go implementującej. Czy to nie łamie jakiś zasad OOP? Dla mnie to było odkrycie roku.

Co chcę dodać w przyszłości:

  • Wspomnianą konfigurację, czyli obsługę plików konfiguracyjnych, itd.
  • Obsługę SignalR do rozgłaszania zdarzeń pojawiających się w systemie.
  • Obsługę EF Core, tylko nie wiem z jaką bazą.
  • Przerobić to na jakiś ambitniejszy "biznes"? Nie wiem, jakiś system aukcyjny?
edytowany 1x, ostatnio: maszrum
somekind
Moderator
  • Rejestracja:około 17 lat
  • Ostatnio:minuta
  • Lokalizacja:Wrocław
2
maszrum napisał(a):

Tak właściwie to ja sam nie wiem co to ma być.

Brzmi jak coś w JS.

Właśnie nie bardzo rozumiem różnicę pomiędzy CQS i CQRS. Oczywiście mogę poczytać regułki z książek, ale jakby nie mogę znaleźć różnicy w kodzie. Czy CQS to jakby "idea", a CQRS to jej implementacja? Coś jak Scrum i Agile?

CQRS jest bardziej na poziomie architektury, a CQS na poziomie kodu. Chociaż pewnie masa fantastyków jednej czy drugiej religii się z tym nie zgodzi.

To chyba trzeba by zrobić jakąś warstwę abstrakcji nad IoC, jego builderem i tak dalej? Ja się tego nie podejmę na pewno. Podoba mi się mój Autofac :P.

Takie rzeczy to robi Microsoft. Normalnie to należy po prostu zapewnić, aby biblioteka/framework miała swoje fabryki/buildery, które na podstawie prostych informacji potrafią zbudować całą potrzebną hierarchię obiektów (używając ich konstruktorów, bez zależności od jakiegoś konkretnego kontenera). Mając takie fabryki, następnie można napisać małą klasę integrującą Twój framework z dowolnym kontenerem (oczywiście każdy kontener musi mieć swoją klasę do integracji).

Tego jeszcze nie zrobiłem, ale mniej więcej mam plan jak to zrobić. Na razie trzymam konfigurację bazy danych "zahardkodowaną". Planuję po prostu wykorzystać konfigurację od ASP.NET Core, wpleść ją jakoś do mojego systemu i ConnectionString trzymać w pliku konfiguracyjnym. Nie wiem, czy to dobre rozwiązanie?

Niespecjalnie widzę alternatywę. Tylko aplikacja asp.net core musi jakoś sensownie ten connection string do Twojego frameworka przekazać.

Nie było żadnych zmian. Udało mi się doprowadzić do takiego rozwiązania, że wystarczy zakomentować jedną linię #define i odkomentować inną w tym pliku. Nie musiałem zmieniać ani jednej linijki w warstwie aplikacji, żeby przejść z jednego sposobu persystencji na drugi. Miałem wydzielony interfejsy read repozytoriów, więc poszło gładko. A write repozytoria komunikują się z warstwą aplikacji przez zdarzenia.

No trochę to dzikie, ale w przykładowym projekcie może przejdzie.

Może to jest banalne, ale ja spędziłem nad tym kawałek czasu, zanim ogarnąłem o co chodzi.
Kiedy pisałem jakiś fragment kodu (mniejsza już jaki), chciałem wywołać metodę obiektu z jakieś Nugetowej biblioteki. Wiedziałem, że na pewno ta metoda istnieje, ale w żadnej sposób IntelliSense nie chciał mi jej podpowiedzieć, a kompilator wywalał błędy. Wtedy po przeszukaniu Stackoverflow odkryłem, że można implementować metody interfejsów w klasie sposobem implicit. Na prawdę nie wiedziałem, że można ukryć publiczną metodę interfejsu w klasie go implementującej. Czy to nie łamie jakiś zasad OOP? Dla mnie to było odkrycie roku.

No, czasem po prostu warto poczytać dobrą książkę do języka. Albo jakąkolwiek, bo w każdej o tym mówią. Tylko to ma sens generalnie, gdy metoda implementowana z interfejsu ma taką samą sygnaturę jak metoda, która już jest w klasie.

edytowany 1x, ostatnio: somekind
maszrum
  • Rejestracja:około 5 lat
  • Ostatnio:12 miesięcy
  • Lokalizacja:Kraków
  • Postów:219
0
somekind napisał(a):

Takie rzeczy to robi Microsoft. Normalnie to należy po prostu zapewnić, aby biblioteka/framework miała swoje fabryki/buildery, które na podstawie prostych informacji potrafią zbudować całą potrzebną hierarchię obiektów (używając ich konstruktorów, bez zależności od jakiegoś konkretnego kontenera). Mając takie fabryki, następnie można napisać małą klasę integrującą Twój framework z dowolnym kontenerem (oczywiście każdy kontener musi mieć swoją klasę do integracji).

Mniej więcej już wiem o co chodzi. Ale mimo wszystko odpuszczę ten temat na razie, chyba by mnie to przerosło.

[...] Planuję po prostu wykorzystać konfigurację od ASP.NET Core, wpleść ją jakoś do mojego systemu i ConnectionString trzymać w pliku konfiguracyjnym. [...]

Niespecjalnie widzę alternatywę. Tylko aplikacja asp.net core musi jakoś sensownie ten connection string do Twojego frameworka przekazać.

W takim razie tak to zrobię. Jak natrafię na jakieś problemy w trakcie pracy to będę dopytywał o konkrety.

Nie było żadnych zmian. Udało mi się doprowadzić do takiego rozwiązania, że wystarczy zakomentować jedną linię #define i odkomentować inną w tym pliku. Nie musiałem zmieniać ani jednej linijki w warstwie aplikacji, żeby przejść z jednego sposobu persystencji na drugi. Miałem wydzielony interfejsy read repozytoriów, więc poszło gładko. A write repozytoria komunikują się z warstwą aplikacji przez zdarzenia.

No trochę to dzikie, ale w przykładowym projekcie może przejdzie.

Dlaczego dzikie? Do której części tej wypowiedzi to się odnosi? To, że przekazuje zdarzenia do warstwy infrastruktury, czy to, że w ogóle nie musiałem nic zmieniać w warstwie aplikacji?

ale w przykładowym projekcie może przejdzie.

Zdaję sobie sprawę, że wszystko działa jak chcę ze względu na banalny projekt i kiedy zrobię bardziej skomplikowany system to moje wyidealizowane wyobrażenie o tym frameworku runie. Dlatego chcę spróbować zrobić coś ambitniejszego. Myślałem nad aukcjami charytatywnymi, czyli coś co często dzieje się na grupkach facebookowych. Myślę, że tam będzie pole do popisu jeśli chodzi o modelowanie i ogólnie DDD. Ale to melodia przyszłości, najpierw chcę posprzątać to co mam.

Brzmi jak coś w JS.

Ja i mój projekt, który dopieszczałem długimi wieczorami z nadzieją, że ktoś doceni.

somekind
Moderator
  • Rejestracja:około 17 lat
  • Ostatnio:minuta
  • Lokalizacja:Wrocław
1
maszrum napisał(a):

Mniej więcej już wiem o co chodzi. Ale mimo wszystko odpuszczę ten temat na razie, chyba by mnie to przerosło.

Tworzenie obiektów przez new?

Dlaczego dzikie? Do której części tej wypowiedzi to się odnosi? To, że przekazuje zdarzenia do warstwy infrastruktury, czy to, że w ogóle nie musiałem nic zmieniać w warstwie aplikacji?

Do dyrektywy kompilatora.

Zdaję sobie sprawę, że wszystko działa jak chcę ze względu na banalny projekt i kiedy zrobię bardziej skomplikowany system to moje wyidealizowane wyobrażenie o tym frameworku runie. Dlatego chcę spróbować zrobić coś ambitniejszego. Myślałem nad aukcjami charytatywnymi, czyli coś co często dzieje się na grupkach facebookowych. Myślę, że tam będzie pole do popisu jeśli chodzi o modelowanie i ogólnie DDD. Ale to melodia przyszłości, najpierw chcę posprzątać to co mam.

Ja bym raczej najpierw zrobił biznes, a potem wydzielił to, co ma sens, bo odwrotnie jest jakieś 90% szans, że nie uda się przewidzieć tego, co rzeczywiście będzie potrzebne.

edytowany 1x, ostatnio: somekind
maszrum
  • Rejestracja:około 5 lat
  • Ostatnio:12 miesięcy
  • Lokalizacja:Kraków
  • Postów:219
0
somekind napisał(a):
maszrum napisał(a):

Mniej więcej już wiem o co chodzi. Ale mimo wszystko odpuszczę ten temat na razie, chyba by mnie to przerosło.

Tworzenie obiektów przez new?

Więc nie jednak nie wiem o co chodzi. Postaram się później jeszcze dokładniej przeanalizować to, co napisałeś wcześniej.

Dlaczego dzikie? Do której części tej wypowiedzi to się odnosi?

Do dyrektywy kompilatora.

Chodziło tylko o to aby w jakiś prosty sposób przełączać pomiędzy tymi linijkami w zależności od tego jaki typ persystencji ma zostać użyty:

Kopiuj

            ContainerBuilder
#if IN_MEMORY_PERSISTENCE
                .RegisterInMemoryPersistence()
                .RegisterAutoTransaction(inMemoryPersistenceAssembly);
#elif DAPPER_PERSISTENCE
                .RegisterDapperPersistence(_ => new DatabaseConfiguration())
                .RegisterAutoTransaction(dapperPersistenceAssembly);
#endif

Może i te dyrektywy nie są zbyt eleganckie (nawet forumowy parser sobie z nimi nie radzi), zaburzają indentacje, ale nie wymyśliłem nic lepszego.

Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)