Modularny monolit - jak najlepiej ugryźć?

Modularny monolit - jak najlepiej ugryźć?
HO
  • Rejestracja:prawie 5 lat
  • Ostatnio:ponad rok
  • Postów:19
0

Cześć,

wyobrażam sobie aplikację podzieloną w ten sposób

Kopiuj
|
|--- module 1
    |
    |- Configuration1.java
|
|--- module 2
    |
    |- Configuration2.java
|
|--- mainModule
    |
    |- Application // tutaj jest main() i wywołanie SpringApplication.run
    |- application.yaml

Oczywiście w uproszczeniu, każdy moduł ma swój build.gradle, testy, kod źródłowy, resources.
Każdy moduł jest samodzielny i nie wie nic na temat świata poza nim. Tylko main module spina wszystko do kupy i uruchamia appkę.
Poprzez gradle wrzucam sobie submoduły do głównego modułu

Kopiuj
implementation(
            project(':module1'),
            project(':module2'),
)

I teraz tak - każdy submoduł ma swój scope i swoje beany. Nie powinny one być widoczne poza modułem (lub nie powinny być widoczne dla innych podmodułów). Każdy submoduł ma swoje serwisy, repozytoria, http kontrolery... W idealnym scenariuszu wyobrażam sobie to tak, że dołączam moduł i mam gotowy nowy feature w aplikacji.
Wiem, że mogę sobie normalnie utworzyć aplikację wielomodułową, ale wtedy mam osobny run aplikacji per moduł. Każdy moduł ma swój SpringApplication.run. W przyszłości możliwe, że będę szedł w mikroserwisy, na teraz jest na to za wcześnie. Potrzebuję dobrze ogarnięty modularny monolit w jednej paczce, jeśli będzie potrzeba to niedużym nakładem pracy podzielę appkę na zupełnie niezależne, osobne moduły.
Ale wracając - każdy submoduł ma swoje beany, np

Kopiuj
@Configuration
class Configuration1 {

    @Value("${property.from.main.module}")
    private String propertyFromMainModule;

    @Bean
    public Bean1 bean1() {
        // using propertyFromMainModule to create the bean
    }
    
}

problem jest taki, że propertisy z application.yaml są zdefiniowane w main module i nie przenikają one do submodułów. Nie da się ich także zdefiniować na poziomie submodułów (osobny application.yaml per moduł - nie ładuje i wyrzuca wyjątek, że nie znaleziono propertisa). Nie da się także zinicjalizować beanów na poziomie main module, bo nie są one widoczne w danym submodule, do którego bean należy.

Macie jakiś pomysł jak wstrzyknąć properties z application yaml do submodułów?
Albo może doradzicie jakiś inny, sprawdzony approach na modularny monolit wg opisanych założeń.

Dzięki!

edytowany 3x, ostatnio: hopsey
Charles_Ray
  • Rejestracja:około 17 lat
  • Ostatnio:około 4 godziny
  • Postów:1874
4
  1. Nie rozumiem dlaczego moduły nie mogą o sobie wiedzieć i potrzebują jakiejś koordynacji poprzez maina - to antypattern (wąskie gardło, będzie mnóstwo konfilktów, współdzielony kod, single point of failure)
  2. Możesz poczytać o hierarchii kontekstów w Springu - nie używałem. Jeśli chodzi o rozdzielenie propertiesów - nie wiem czy to w tym momencie nie jest wtórny problem, który wynika z użytego frameworka, a którego rozwiązanie niewiele daje
  3. Zamiast modułów Gradlowych możesz zrobić pakiety - ja migrowałem się właśnie z modułów na pakiety ze względu łatwości developmentu (za długo trwa wynoszenie wspólnych części do osobnego modułu)
  4. Możesz użyć ArchUnit do walidacji założeń architektonicznych

”Engineering is easy. People are hard.” Bill Coughran
edytowany 3x, ostatnio: Charles_Ray
superdurszlak
Zaintrygował mnie ten ArchUnit - jakie ma ciekawe możliwości? Z tego co widzę po przykładowych testach, to pokazali głównie "sanity checki" - konwencje, szukanie cykli, niewołanie jednego kontrolera z drugiego...
Charles_Ray
Np. takie, że z warstwy domeny nie można wołać niczego z infrastruktury. W zasadzie można tam sprawdzać dowolne konwencje przyjęte w projekcie.
danek
pakiety mają te wadę, że są płaskie. Nie da się ich sensownie zagnieżdżać. Dodatkowo w kotlinie i tak nie działają :(
TS
  • Rejestracja:ponad 4 lata
  • Ostatnio:ponad 4 lata
  • Postów:394
2

Do SpringApplication.run można przekazać wiele klas. Pojęcia nie mam jak to zadziała w praktyce, nigdy nie próbowałem, ale może jak tam przekażesz niewielką konfiguracją z main, która ładuje te propertiesy to będą one widoczne w poszczególnych submodułach?

Aventus
  • Rejestracja:około 9 lat
  • Ostatnio:ponad 2 lata
  • Lokalizacja:UK
  • Postów:2235
1

Jeśli każdy moduł ma swoje kontrolery HTTP to brzmi to bardziej jakbyś miał mikroserwisy a nie modularny monolit. Przecież możesz mieć jeden serwis API który będzie delegował pracę do konkretnego modułu, a więc kontrolery mogą być zdefiniowane w jednym miejscu. Aby to osiągnąć proponuję poczytać o wzrocu mediator. Moduły powinny być granicą konkretnej sub-domeny Twojej aplikacji, a więc przede wszystkim skupiać się na oddzieleniu logiki biznesowej. Infrastruktura to sprawa drugorzędna.


Na każdy złożony problem istnieje rozwiązanie które jest proste, szybkie i błędne.
Charles_Ray
Czyli gdzie byś trzymał kontrolery? Załóżmy, że modułów enkapsulujacych bounded conteksty jest 20.
Aventus
@Charles_Ray: Przy takiej złożoności to zastanowiłbym się właśnie nad mikroserwisami. W przeciwnym razie kontrolery trzymał bym tam gdzie powinny być, czyli w programie webowym wystawiającym API. Tutaj ważna uwaga- zakładam że mówimy o kontrolerach które służą do tego do czego powinny służyć, czyli jedynie oddelegowania poleceń dalej, do warstwy domenowej, oraz do wszystkiego co związane z obsługą HTTP. Żadnej logiki biznesowej itp.
Charles_Ray
A jaki problem rozwiążą tutaj mikroserwisy?
Aventus
Przy takiej ilości kontekstów masz z pewnością bardzo złożoną domenę (oczywiście zakładam że nie są to źle wydzielone konteksty). Zapewne każdy musi wystawić jakieś API, a więc idąc tym tokiem zapewne mamy różne wymagania co do skalowalności poszczególnych kontekstów- raczej mało prawdopodobne że każdy jest używany z taką samą intensywnością, i w taki sam sposób (jeden np. może obsługiwać interakcje użytkownika, ale inny już odpowiadać na jakieś zautomatyzowane procesy lub konfigurację wykonywana przez wewnętrzny personel). Przy takiej różnorodności...
Aventus
... potrzeba autonomii poszczególnych modułów aż się sama nasuwa na myśl. A takiej autonomii nie osiągniesz przy monolicie, tam gdzie masz deployment jednego artefaktu lub góra kilku, ale deployowoanych razem. Do tego podział na serwisy wymusi "fizycznie" odpowiednie rozdzielenie modułów, zarówno w sensie podziału domeny jak i na poziomie infrastruktury. Co za tym idzie łatwiej utrzymać samodyscyplinę między zespołami.
danek
  • Rejestracja:ponad 10 lat
  • Ostatnio:6 miesięcy
  • Lokalizacja:Poznań
  • Postów:797
2

U mnie obecnie wygląda to tak: jest jeden moduł na api restowe. Ma on zależność na wszystkie moduły, których potrzebuje. Klasy zazwyczaj widzi przez interface. W jednym module siedzi zazwyczaj jedna funkcjonalność, która korzysta z modułów, które komunikują się z np baża danych. I tak przykładowo dla prostej funkcjonalności, która na request odczytuje coś z bazy:
Moduł api: jest tutaj rest controller, widzi moduł domenowy
Moduł domenowy: tutaj jakaś logika, filtrowanie cokolwiek, widzi moduł do komunikacji z bazą.
Moduł bazy danych: bezpośredni odczyt danych i zamiana ich na jakiś sensowny format

Jakie to ma wady? Modele. Dużo modeli. Idealnie jakby każdy moduł miał swój zestaw modeli na jeden byt, ponieważ może wtedy sobie np dowolnie ucinać zbędne dany. Dla przykładu obiekt z bazy ma pola A,B,C. W domenie potrzebujesz tylko A i przemapować B na podstawie danych z innego miejsca. W api chcesz zwrócić tylko A w zależności od B. I tak na każdym etapie operujesz tylko na tych polach których faktycznie potrzebujesz. Niestety, bawisz się w mapowanie wszystkiego zawsze.

Przy okazji możesz się zainteresować architekturą hexagonalną, ponieważ ładnie do tego pasuje
https://herbertograca.com/2017/11/16/explicit-architecture-01-ddd-hexagonal-onion-clean-cqrs-how-i-put-it-all-together/


Spring? Ja tam wole mieć kontrole nad kodem ᕙ(ꔢ)ᕗ
Haste - mała biblioteka do testów z czasem.
edytowany 2x, ostatnio: danek
HO
  • Rejestracja:prawie 5 lat
  • Ostatnio:ponad rok
  • Postów:19
0
Charles_Ray napisał(a):
  1. Nie rozumiem dlaczego moduły nie mogą o sobie wiedzieć i potrzebują jakiejś koordynacji poprzez maina - to antypattern (wąskie gardło, będzie mnóstwo konfilktów, współdzielony kod, single point of failure)
  2. Możesz poczytać o hierarchii kontekstów w Springu - nie używałem. Jeśli chodzi o rozdzielenie propertiesów - nie wiem czy to w tym momencie nie jest wtórny problem, który wynika z użytego frameworka, a którego rozwiązanie niewiele daje
  3. Zamiast modułów Gradlowych możesz zrobić pakiety - ja migrowałem się właśnie z modułów na pakiety ze względu łatwości developmentu (za długo trwa wynoszenie wspólnych części do osobnego modułu)
  4. Możesz użyć ArchUnit do walidacji założeń architektonicznych

To nie jest tak, że main koordynuje ich działanie. Main tylko bootstrapuje aplikację. Gdybym chciał pójść w mikroserwisy, mógłbym go całkowicie usunąć.
Też nie jest tak, że nic o sobie nie wiedzą. Są podzielone według domeny a komunikują się zdarzeniami. Moduł faktur wie o zdarzeniu order.paid modułu zamówień i samodzielnie wystawia fakturę, na koniec emitując odpowiednie zdarzenie.
Mam też moduł z częścią wspólną (interfejsy, commandbus, eventbus, itp), który jest widziany przez każdy moduł.
Tak jak napisałeś, skłaniam się do porzucenia modułów, rozwinięcia pakietów wewnątrz jednego modułu, co bardziej wpisuje się w monolit.

tsz napisał(a):

Do SpringApplication.run można przekazać wiele klas. Pojęcia nie mam jak to zadziała w praktyce, nigdy nie próbowałem, ale może jak tam przekażesz niewielką konfiguracją z main, która ładuje te propertiesy to będą one widoczne w poszczególnych submodułach?

Czyli każdy moduł ma swoją klasę ze @SpringBootApplication? Sprawdzę nawet z ciekawości jak do działa.

Aventus napisał(a):

Jeśli każdy moduł ma swoje kontrolery HTTP to brzmi to bardziej jakbyś miał mikroserwisy a nie modularny monolit. Przecież możesz mieć jeden serwis API który będzie delegował pracę do konkretnego modułu, a więc kontrolery mogą być zdefiniowane w jednym miejscu. Aby to osiągnąć proponuję poczytać o wzrocu mediator. Moduły powinny być granicą konkretnej sub-domeny Twojej aplikacji, a więc przede wszystkim skupiać się na oddzieleniu logiki biznesowej. Infrastruktura to sprawa drugorzędna.

Faktycznie, trochę bliżej do mikroserwisów. Plan jest taki, że kiedyś aplikacja zostanie podzielona na mikroserwisy, dlatego wszystko jest dosyć radykalnie odseparowane. Łatwiej będzie wydzielić moduł do pojedynczego mikroserwisu. Cała warstwa aplikacji (każdy punkt wejścia, w tym http) steruje domeną przez commandy. CommandBus dispatchuje komendę wywołując odpowiedni handler, ten dealuje już z agregatami, perzystuje zdarzenia (mam ES).
Moduły są podzielone według domeny. Jak napisałem wyżej, zrzucę to wszystko do pakietów, httpa wyrzucę poza moduły.

Charles_Ray
  • Rejestracja:około 17 lat
  • Ostatnio:około 4 godziny
  • Postów:1874
1

Odnośnie sposobie podziału na moduły i komunikacji między nimi wydaje mi się to OK. Pamiętaj tylko, że obecnie masz monolit, a wiec jeden JVM. Dodanie większej liczby adnotacji niewiele tutaj pomoże. Pójście w mikroserwisy nie zawsze ma sens i się opłaca - masz potrzebę niezależnego skalowania modułów lub wiele niezależnych zespołów?


”Engineering is easy. People are hard.” Bill Coughran
edytowany 1x, ostatnio: Charles_Ray
HO
  • Rejestracja:prawie 5 lat
  • Ostatnio:ponad rok
  • Postów:19
0
danek napisał(a):

U mnie obecnie wygląda to tak: jest jeden moduł na api restowe. Ma on zależność na wszystkie moduły, których potrzebuje. Klasy zazwyczaj widzi przez interface. W jednym module siedzi zazwyczaj jedna funkcjonalność, która korzysta z modułów, które komunikują się z np baża danych. I tak przykładowo dla prostej funkcjonalności, która na request odczytuje coś z bazy:
Moduł api: jest tutaj rest controller, widzi moduł domenowy
Moduł domenowy: tutaj jakaś logika, filtrowanie cokolwiek, widzi moduł do komunikacji z bazą.
Moduł bazy danych: bezpośredni odczyt danych i zamiana ich na jakiś sensowny format

Jakie to ma wady? Modele. Dużo modeli. Idealnie jakby każdy moduł miał swój zestaw modeli na jeden byt, ponieważ może wtedy sobie np dowolnie ucinać zbędne dany. Dla przykładu obiekt z bazy ma pola A,B,C. W domenie potrzebujesz tylko A i przemapować B na podstawie danych z innego miejsca. W api chcesz zwrócić tylko A w zależności od B. I tak na każdym etapie operujesz tylko na tych polach których faktycznie potrzebujesz. Niestety, bawisz się w mapowanie wszystkiego zawsze.

Przy okazji możesz się zainteresować architekturą hexagonalną, ponieważ ładnie do tego pasuje
https://herbertograca.com/2017/11/16/explicit-architecture-01-ddd-hexagonal-onion-clean-cqrs-how-i-put-it-all-together/

U mnie to jest ogarnięte tak

  • warstwa aplikacji - httpy, commandbusy, eventbusy, listenery, utilsy, etc.
  • warstwa domeny - agregaty, vo, serwisy domenowe, inaczej - domain objects
  • warstwa infry - adaptery do portów z warstwy aplikacji i domeny (tam są właściwie same interfejsy) - do perzystencji, zdarzeń itp. Np w tej chwili mając event driven architecture wewnątrz monolitu / jednej aplikacji nie potrzebuję message brokera. Ogarniam to przez ApplicationEventPublisher. Ale o tym wie tylko infra, warstwy wyzej nic o tym nie wiedza, bo wala w interfejs. Jak zaczniemy używać architektury mikroserwisowej, wówczas zmienimy tylko rozszerzenie eventbusa w infrastrukturze, który będzie już walił w rabbita czy inną kafkę.

Co do modeli - w związku z tym, że mam cqrs na full tworzę osobny moduł, który przechwytuje zdarzenia i buduje read model. Wszystkie gety tam mają lecieć. W zależności od kontekstu buduję odpowiedni model widoku. Jeśli w kontekście danego bytu potrzebuję pola A B i C, tak zostawiam. Jeśli mam coś zmienić/przemapować, tworzę osobny widok, itd.

Generalnie w pytaniu chodziło mi o technikalia dotyczące modułów javowych, z którymi nie mam zbyt dużego doświadczenia. Na teraz wydaje mi się, że pakiety są najlepszym kompromisem w tym co chciałbym osiągnąć, nie tracąc jednocześnie ścisłej izolacji modułów wg domeny.

edytowany 1x, ostatnio: hopsey
HO
  • Rejestracja:prawie 5 lat
  • Ostatnio:ponad rok
  • Postów:19
0
Charles_Ray napisał(a):

Odnośnie sposobie podziału na moduły i komunikacji między nimi wydaje mi się to OK. Pamiętaj tylko, że obecnie masz monolit, a wiec jeden JVM. Dodanie większej liczby adnotacji niewiele tutaj pomoże. Pójście w mikroserwisy nie zawsze ma sens i się opłaca - masz potrzebę niezależnego skalowania modułów lub wiele niezależnych zespołów?

niezależnego skalowania modułów, ale jeszcze nie teraz. w tej chwili to byłby zdecydowanie niepotrzebny, dodatkowy narzut. warstwa odpowiadające za kwerendy (mam cqrsa) będzie dosyć mocno orana, w czasie kiedy domena trochę mniej. jestem pewien, że prędzej czy później cały read model trzeba będzie rozstawić na kilku podach.

Charles_Ray
Oki! Brzmi sensownie :) jedno zastrzeżenie - skoro masz CQRS to dlaczego nie możesz zreplikować bazy/indeksu? Po stronie aplikacji nie będzie dużo do roboty
HO
@Charles_Ray: nie wiem czy dobrze rozumiem i czy to właściwa odpowiedź - mam też ES, nie przechowuję modeli w całości (tylko przy domenie - oczywiście tak gdzie crud tam jest crud). Nie da się z nich nic wyczytać. No chyba że jakieś widoki na PG ;)
Charles_Ray
Napisałeś, że read model będzie trzeba skalować, czyli ES nie ma nic do tego ;)
HO
@Charles_Ray: ok, już kumam - tak, może i replikacja db warstwy widokowej by wystarczyła... nie chcę jednak zamykać furtki na smooth przejście w MS :)
nie100sowny
  • Rejestracja:prawie 9 lat
  • Ostatnio:dzień
  • Lokalizacja:Kraków
  • Postów:402
0
danek napisał(a):

Moduł domenowy: tutaj jakaś logika, filtrowanie cokolwiek, widzi moduł do komunikacji z bazą.
[,,,]
Przy okazji możesz się zainteresować architekturą hexagonalną, ponieważ ładnie do tego pasuje

W architekturze hexagonalnej to moduł bazy danych widzi domenowy, a domenowy nie widzi bazy danych. Zalezność odwrócona.

API --> DOMAIN <-- STORAGE

Twoja domena teraz zależy od szczegółów bazy danych.


"Gdy się nie wie, co się robi, to się dzieją takie rzeczy, że się nie wie, co się dzieje"
edytowany 3x, ostatnio: nie100sowny
danek
widzi w sumie tylko port, ale tak, masz rację 
damianem
  • Rejestracja:prawie 8 lat
  • Ostatnio:3 miesiące
  • Postów:205
2

Według mnie przy tym co chcesz osiągnąć, powinieneś tworzyć osobny application context dla każdego modułu i ustawiać na nim parent context z Twojego main modułu. W taki sposób konteksty modułów powinny mieć dostęp do propertiesów parenta.
Żeby zachować niezależność modułów, dodałbym osobny moduł, nazwijmy go module-api (moduły i main powinny mieć na niego zależność) gdzie umieściłbym taki interface:

Kopiuj
interface MyAppModule {
    ApplicationContext createModuleContext(ApplicationContext parent);
}

Główna klasa w każdym module może wtedy implementować taki interface a główny moduł może poprzez mechanizm SPI ładować dynamicznie zadeklarowane implementacje i inicjalizować moduły, używając createModuleContext i podając swój context jako parent.

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)