DDD - model domeny a warstwa persystencji

DDD - model domeny a warstwa persystencji
KO
KO
  • Rejestracja:ponad 6 lat
  • Ostatnio:ponad rok
  • Postów:9
0

Cześć,

od pewnego czasu zgłębiam powyższy temat i o ile w teorii wszystko wydaje się piękne o tyle w praktyce podczas implementacji już nie.

Załóżmy poniższy model realizujący logikę BoundedContext: OrderManagement

Kopiuj
    class Order : AggregateRoot
    {
        private readonly List<OrderItem> _items = new List<OrderItem>();
        public IEnumerable<OrderItem> Items => _items;

        public void AddProduct(Product product, int quantity)
        {
            _items.Add(new OrderItem(product, quantity));
        }

        public void DeleteProduct(Product product)
        {
            var item = _items.First(i => i.Product == product);

            _items.Remove(item);
        }
    }

    class OrderItem
    {
        public Product Product { get; }
        public int Quantity { get; }

        internal OrderItem(Product product, int quantity)
        {
            Product = product;
            Quantity = quantity;
        }
    }

    class Product
    {
        public int AvailableQuantity { get; }
        public int AvailableQuantityMax { get; }
    }

Jedno z wymagań do powyższego modelu to:

  • aktualizacja dostępnych sztuk produktu (AvailableQuantity) podczas dodawania, usuwania i edycji pozycji zamówienia.

Pierwotnie myślałem o dwóch metodach w encji Product o zakresie internal, które to odpowiednio zmieniają ilość dostępnych sztuk produktu.
Metody te chciałem wykorzystywać z poziomu AggregateRoot podczas AddProduct, DeleteProduct

Problemy pojawiły się podczas implementacji repozytorium dla AggregateRoot w zakresie operacji usuwania pozycji zamówienia oraz całego zamówienia.
Pod pojęciem usuwanie mam na myśli trwałe usunięcie tych danych z bazy danych (zostawmy dyskusję czemu lepiej nie kasować a oznaczać flagą).

Usuwając pozycję zamówienia tracę dostęp do obiektu Product, który to musi w warstwie persystencji zostać zapisany.

Jak powinienem zaimplementować taką fikcjonalność pamiętając o persistence ignorance?

Miang
  • Rejestracja:prawie 7 lat
  • Ostatnio:około 4 godziny
  • Postów:1659
1

co to znaczy dostępne?


dzisiaj programiści uwielbiają przepisywać kod z jednego języka do drugiego, tylko po to by z projektem nadal stać w miejscu ale na nowej technologii
neves
  • Rejestracja:ponad 21 lat
  • Ostatnio:około 12 godzin
  • Lokalizacja:Kraków
  • Postów:1114
1

Jeśli potrzebujesz bezpośredni dostęp do encji która wchodzi w skład AggregateRoota, to zwykle oznacza że trzeba z niej zrobić kolejnego AggregateRoota, problem solved.


E9
  • Rejestracja:ponad 13 lat
  • Ostatnio:10 miesięcy
  • Postów:395
3

Ah te crudy w DDD. Czy to będzie nowy rak w IT ?

somekind
Przynajmniej filozofowie i książkowi teoretycy mają o czym pogadać. Lepsze to, niż gdyby napadali staruszki na ulicy. ;)
Gworys
  • Rejestracja:około 6 lat
  • Ostatnio:ponad 5 lat
  • Postów:139
0
neves napisał(a):

Jeśli potrzebujesz bezpośredni dostęp do encji która wchodzi w skład AggregateRoota, to zwykle oznacza że trzeba z niej zrobić kolejnego AggregateRoota, problem solved.

To nieprawda i nie jest to dobre rozwiązanie.
Dostęp do Encji czy Value Object możesz zapewnić przez Serwis nie musisz pobierać tego przez repozytorium.

Poza tym mylisz Encje z Value Object i widać to na kodzie, który pokazujesz. Wielokrotnie już pisałem, że jest to powszechne. Prawdopodobnie masz złe konteksty, bo prawdopodobnie jeden produkt to Aggregat a drugi jak sam widzisz Value Object.


Unhandled Exception: System.MissingMethodException: Constructor on type 'System.Exception' not found.
Gworys
  • Rejestracja:około 6 lat
  • Ostatnio:ponad 5 lat
  • Postów:139
1
error91 napisał(a):

Ah te crudy w DDD. Czy to będzie nowy rak w IT ?

Rakiem IT są ludzie. Równie dobrze można przypinać do projektu etykietę Agillity, bo akurat użyłem wzorca z tej książki.


Unhandled Exception: System.MissingMethodException: Constructor on type 'System.Exception' not found.
KO
KO
  • Rejestracja:ponad 6 lat
  • Ostatnio:ponad rok
  • Postów:9
0
neves napisał(a):

Jeśli potrzebujesz bezpośredni dostęp do encji która wchodzi w skład AggregateRoota, to zwykle oznacza że trzeba z niej zrobić kolejnego AggregateRoota, problem solved.

Z perspektywy modelu domeny nie potrzebuję takiego dostępu. Potrzebuję go dopiero podczas utrwalania zmian w warstwie persystencji.

Gworys
  • Rejestracja:około 6 lat
  • Ostatnio:ponad 5 lat
  • Postów:139
0
Kombajnator napisał(a):
neves napisał(a):

Jeśli potrzebujesz bezpośredni dostęp do encji która wchodzi w skład AggregateRoota, to zwykle oznacza że trzeba z niej zrobić kolejnego AggregateRoota, problem solved.

Z perspektywy modelu domeny nie potrzebuję takiego dostępu. Potrzebuję go dopiero podczas utrwalania zmian w warstwie persystencji.

No to prawdopodobnie potrzebujesz IdentityField, który będzie w agregacie Order i Product, a ten produkt w agregacie Order zmień na ProductAvailability.


Unhandled Exception: System.MissingMethodException: Constructor on type 'System.Exception' not found.
KO
KO
  • Rejestracja:ponad 6 lat
  • Ostatnio:ponad rok
  • Postów:9
0
Gworys napisał(a):
Kombajnator napisał(a):
neves napisał(a):

Jeśli potrzebujesz bezpośredni dostęp do encji która wchodzi w skład AggregateRoota, to zwykle oznacza że trzeba z niej zrobić kolejnego AggregateRoota, problem solved.

Z perspektywy modelu domeny nie potrzebuję takiego dostępu. Potrzebuję go dopiero podczas utrwalania zmian w warstwie persystencji.

No to prawdopodobnie potrzebujesz IdentityField, który będzie w agregacie Order i Product, a ten produkt w agregacie Order zmień na ProductAvailability.

Nie rozumiem co masz na myśli. Nie wiem co zmienia wprowadzenie IdField oraz czemu w zamówieniu mam trzymać informację o pozostałej ilości dostępnych sztuk produktu.

Gworys
  • Rejestracja:około 6 lat
  • Ostatnio:ponad 5 lat
  • Postów:139
2

Ach, czekaj nie doczytałem.

aktualizacja dostępnych sztuk produktu (AvailableQuantity) podczas dodawania, usuwania i edycji pozycji zamówienia.

To zrób usługę domenową, która będzie operować na dwóch agregatach. Order i Product

Albo niech repozytorium Order puści event dodano zamówienie a subskrybentem będzie usługa odejmująca ilość produktów.


Unhandled Exception: System.MissingMethodException: Constructor on type 'System.Exception' not found.
edytowany 3x, ostatnio: Gworys
KO
KO
  • Rejestracja:ponad 6 lat
  • Ostatnio:ponad rok
  • Postów:9
0
Gworys napisał(a):

Ach, czekaj nie doczytałem.

aktualizacja dostępnych sztuk produktu (AvailableQuantity) podczas dodawania, usuwania i edycji pozycji zamówienia.

To zrób usługę domenową, która będzie operować na dwóch agregatach. Order i Product

Albo niech repozytorium Order puści event dodano zamówienie a subskrybentem będzie usługa odejmująca ilość produktów.

Nie jestem przekonany co do podejścia opartego o eventy. Bardziej szedłbym właśnie w stronę usługi domenowej.
Niezależnie od wybranej ścieżki, rodzi mi się w tym momencie kolejne pytanie, związane już bezpośrednio z samymi repozytoriami tworzonymi dla poszczególnych agregatów.
Gdzie powinna przebiegać ich granica odpowiedzialności za utrwalenie danych zwłaszcza gdy trzeba zachować spójność podczas transakcji?

Czy na zakończenie poszczególnych metod (Insert/Update/Delete ew. Save) w repozytorium powinienem wyzwalać Commit na bazie danych?
Początkowo myślałem, że tak ale teraz widzę iż nie jest to jakoś narzucone przez DDD i mogę w ramach danego BC utworzyć interfejs IUnitOfWork dzięki czemu mogę:

Kopiuj
_orderRepository.Update(order);
_productRepository.Update(product);
_unitOfWork.Commit();
Gworys
  • Rejestracja:około 6 lat
  • Ostatnio:ponad 5 lat
  • Postów:139
0

@Kombajnator

Repozytorium ma działać tak jakbyś coś, z niego wyciągał przez referencje.
Poza tym niby po co ci tam Update?


Unhandled Exception: System.MissingMethodException: Constructor on type 'System.Exception' not found.
KO
KO
  • Rejestracja:ponad 6 lat
  • Ostatnio:ponad rok
  • Postów:9
0
Gworys napisał(a):

@Kombajnator

Repozytorium ma działać tak jakbyś coś, z niego wyciągał przez referencje.
Poza tym niby po co ci tam Update?

Jak to poco?
Repozytorium służy przecież również do utrwalenia danych.
Wiem, że jeżeli korzystam z ORM to mogę przez metodę GetProduct zwrócić obiekt agregatu, który pozostanie "w trybie change tracking".
Nie wiem tego jednak po stronie warstwy logiki aplikacji.

W warstwie domeny mam definicję interfejsu repozytorium.
W warstwie infrastruktury mam implementację tegoż repozytorium.
W warstwie aplikacji (np. command hadler z CQRS) nie mogę zakładać, że obiekt agregatu zwrócony przez repo jest "w jakimś trybie śledzenia zmian".

Tak więc repozytorium musi mieć metody odpowiedzialne za zapis zmian.
Tak to przynajmniej rozumiem patrząc już z szerszej perspektywy.

wiewiorek
  • Rejestracja:prawie 17 lat
  • Ostatnio:10 dni
0

Kurde @Kombajnator założyłeś temat, który i mnie jakiś czas temu interesował - miałem IDENTYCZNE pytania :D Nie znalazłem odpowiedzi :/
Repozytorium jest odpowiedzialne za odczyt **ORAZ ** zapis z/do jakiegoś magazynu danych:

Kopiuj
public class ProductRepository
{
	private readonly DatabaseContext _databaseContext;

	public ProductRepository(DatabaseContext databaseContext)
	{
		_databaseContext = databaseContext;
	}

	public async Task<Product> GetProduct(Guid productId)
	{
		return await _databaseContext.Products.SingleAsync(x => x.ProductId == productId);
	}

	public async Task AddProduct(Product product)
	{
		await _databaseContext.Products.AddAsync(product);
                // dawac tu _databaseContext.Save() czy nie
	}
}

Pytanie czy wywoływać metodę Save() w repozytorium czy nie. W większości przykładów dostępnych w sieci - Save() jest wywoływany w repozytorium. Ale co w sytuacji jak zmiana ma miejsce w dwóch repozytoriach i wówczas dopiero chcemy zatwierdzić transakcję - unit of work tak jak zrobił autor wątku wydaje się sensowny. Ale ktoś ma może inne doświadczenia?

Druga kwestia, co z tym usuwaniem - w sieci ja też szukałem jakiś czas temu i nic nie znalazłem - przykładowo naszym agregatem jest zamówienie - z jakiegoś powodu chcielibyśmy usunąć zamówienie całkowicie z bazy danych. Czy dobrym pomysłem jest żeby agregat usuwał sam siebie w repozytorium?

Gworys
  • Rejestracja:około 6 lat
  • Ostatnio:ponad 5 lat
  • Postów:139
0

Nie bardzo was rozumiem, jeśli stworzysz instancje "produktu" w serwisie z ORM'a to oznacza to, że ona przestaje istnieć w repozytorium i ORM'ie...? Albo w jakiś magiczny sposób jest odporna na zmiany, które wykonujesz wyżej w serwisie.?

A jaki problem rozwiązuje Unit Of Work.?


Unhandled Exception: System.MissingMethodException: Constructor on type 'System.Exception' not found.
edytowany 2x, ostatnio: Gworys
wiewiorek
  • Rejestracja:prawie 17 lat
  • Ostatnio:10 dni
0

Czyli jak jednocześnie chcesz w transakcji modyfikować dane klienta i jego zamówienie to robisz to po prostu w serwisie?

Gworys
  • Rejestracja:około 6 lat
  • Ostatnio:ponad 5 lat
  • Postów:139
0
wiewiorek napisał(a):

Czyli jak jednocześnie chcesz w transakcji modyfikować dane klienta i jego zamówienie to robisz to po prostu w serwisie?

Tak w domenowy, ale lepsze rozwiązanie jest te bazujące na eventach.

No i dlaczego nikt mnie nie poprawia.? Nawet dwa like za to dostałem :D

Tak na serio to powinieneś mieć tylko wspólny Value Object z ilością produktów dla dwóch agregatów Order i Product.


Unhandled Exception: System.MissingMethodException: Constructor on type 'System.Exception' not found.
edytowany 4x, ostatnio: Gworys
KO
KO
  • Rejestracja:ponad 6 lat
  • Ostatnio:ponad rok
  • Postów:9
0
Gworys napisał(a):

Nie bardzo was rozumiem, jeśli stworzysz instancje "produktu" w serwisie z ORM'a to oznacza to, że ona przestaje istnieć w repozytorium i ORM'ie...? Albo w jakiś magiczny sposób jest odporna na zmiany, które wykonujesz wyżej w serwisie.?

A jaki problem rozwiązuje Unit Of Work.?

Nie chodzi o to, że przestaje istnieć lub jakąś inną magię :D
Chodzi o podział odpowiedzialności poszczególnych warstw.

Dla tego właśnie spytałem o granice odpowiedzialności implementacji repozytorium dla danego agregatu.

Przyjmijmy implementację repo. opartą o Entity Framework.
Repozytorium posiada metodę GetProduct, która zwraca obiekt żądanego agregatu.
Zmiany wprowadzone w tym obiekcie są "śledzone" przez mechanizm EF.
Można zatem po stronie logiki aplikacji wykonać taki kod:

Kopiuj
var product = _repo.GetProduct(1);
product.GetAmount(8);
?????.SaveChanges();

Co jest nie tak z takim podejściem?
A no to, że zmiana implementacji repo pociąga za sobą zmiany w logice aplikacji (wywalasz EF i ożywasz ADO - SaveChanges przestaje działać).
Właśnie takich zależności między innymi chcemy unikać wprowadzając podziały na warstwy. Czyż nie?

Posiadając wspomnianą definicję interfejsu UoW po stronie logiki aplikacji mogę:

Kopiuj
_orderRepository.Update(order);
_productRepository.Update(product);
_unitOfWork.Commit();

Kontener DI odpowiada za dostarczenie tej samej instancji DB Context'u zarówno do repozytoriów jak i "pod zmienną _unitOfWork".

Kopiuj
namespace OrderManagement.Data.Repositories
{
    internal class OrderRepository : IOrderRepository
    {
        private readonly DatabaseContext _db;

        public OrderRepository(DatabaseContext db)
        {
            _db = db;
        }
		
		...
    }
}

namespace OrderManagement.Data
{
	internal class DatabaseContext : DbContext, IUnitOfWork
	{
		public void Commit() => Database.CurrentTransaction.Commit();
	}
}

namespace OrderManagement.App.Database
{
    public interface IUnitOfWork
    {
        void Commit();
    }
}

Dzięki temu zmiana implementacji warstwy persystencji nie pociąga za sobą zmiany w innych warstwach.

Gworys napisał(a):
wiewiorek napisał(a):

Czyli jak jednocześnie chcesz w transakcji modyfikować dane klienta i jego zamówienie to robisz to po prostu w serwisie?

Tak w domenowy, ale lepsze rozwiązanie jest te bazujące na eventach.

No i dlaczego nikt mnie nie poprawia.? Nawet dwa like za to dostałem :D

Tak na serio to powinieneś mieć tylko wspólny Value Object z ilością produktów dla dwóch agregatów Order i Product.

Możesz rozwinąć myśl (koncepcję) związaną z eventami?
Jak wygenerowanie eventu ma zapewnić spójność?

ValueObject tu? Hmm... To chyba niezbyt trafne zastosowanie.

edytowany 1x, ostatnio: Kombajnator
Gworys
  • Rejestracja:około 6 lat
  • Ostatnio:ponad 5 lat
  • Postów:139
0

ValueObject tu? Hmm... To chyba niezbyt trafne zastosowanie.

Dlaczego.? Mówiłeś coś wcześniej o abstrakcjach warstw, no więc dlaczego.? :D

A no to, że zmiana implementacji repo pociąga za sobą zmiany w logice aplikacji (wywalasz EF i ożywasz ADO - SaveChanges przestaje działać).
Właśnie takich zależności między innymi chcemy unikać wprowadzając podziały na warstwy. Czyż nie?

No bo trzeba wtedy samemu zaimplementować UOW. To że coś jest na dole zepsute to nie znaczy, że trzeba górę zmieniać.
Możesz sobie zrobić zamiast commit w UOW metodę Save zamiast Add w repozytorium, książkowo nazywa się to repozytorium typu magazyn trwały.

Możesz rozwinąć myśl (koncepcję) związaną z eventami?
Jak wygenerowanie eventu ma zapewnić spójność?

No niemoże a nawet nie powinno.


Unhandled Exception: System.MissingMethodException: Constructor on type 'System.Exception' not found.
edytowany 1x, ostatnio: Gworys
KO
KO
  • Rejestracja:ponad 6 lat
  • Ostatnio:ponad rok
  • Postów:9
0
Gworys napisał(a):

ValueObject tu? Hmm... To chyba niezbyt trafne zastosowanie.

Dlaczego.? Mówiłeś coś wcześniej o abstrakcjach warstw, no więc dlaczego.? :D

Myślałem właśnie, że Ty mi uzasadnisz tą decyzję :)

When you care only about the attributes and logic of an element of the model, classify it as a value object. Make it express the meaning of the attributes it conveys and give it related functionality. Treat the value object as immutable.

When an object is distinguished by its identity, rather than its attributes, make this primary to its definition in the model. Keep the class definition simple and focused on life cycle continuity and identity.

Zmieniając ilość dostępnych sztuk nie zmieniam tożsamości produktu. Dla tego uważam, że powinien pozostać Encją.
Dla tego też przychylam się do opcji serwisu domenowego niż do ValueObject.

Gworys napisał(a):

A no to, że zmiana implementacji repo pociąga za sobą zmiany w logice aplikacji (wywalasz EF i ożywasz ADO - SaveChanges przestaje działać).
Właśnie takich zależności między innymi chcemy unikać wprowadzając podziały na warstwy. Czyż nie?

No bo trzeba wtedy samemu zaimplementować UOW. To że coś jest na dole zepsute to nie znaczy, że trzeba górę zmieniać.
Możesz sobie zrobić zamiast commit w UOW metodę Save zamiast Add w repozytorium, książkowo nazywa się to repozytorium typu magazyn trwały.

Posłużyłem się przykładem ADO aby zaznaczyć różnicę jaka może się pojawić gdy do komunikacji z bazą danych użyjemy zupełnie innego mechanizmu.
Implementuję warstwę persystencji przy użyciu ADO - sorry, moja świadoma decyzja o nakładzie pracy jaki mnie czeka.
Zmieniam użyty ORM w warstwie persystencji czy też samo miejsce zapisania zmian - też czeka nie zmiana implementacji repo ale tylko w jej granicach.

Przecież takie zmiany nie powinny implikować konieczności zmian po stronie innych warstw. Inaczej repozytorium implementowane na potrzeby DDD staje się abstrakcją na abstrakcję.
Przykładowy EF już sam w sobie jest implementacją Repozytorium jak i UoW.

Gworys napisał(a):

Możesz rozwinąć myśl (koncepcję) związaną z eventami?
Jak wygenerowanie eventu ma zapewnić spójność?

No niemoże a nawet nie powinno.

Czyli te podejście odpada :)

Gworys
  • Rejestracja:około 6 lat
  • Ostatnio:ponad 5 lat
  • Postów:139
0

Problem polega na tym, że składanie zamówienia to nie jest banalna transakcją na dwóch tabelach.

Powinieneś pobrać ValueObject z np. kontekstu magazynu, który będzie zawierać id item'u w magazynie info o rozmiarze, wadze oraz ilości sztuk.

Po zakitowaniu płatności powinien być puszczony event z ilością sztuk do zmiany w magazynie.

Całość to proces długotrwały (Saga).


Unhandled Exception: System.MissingMethodException: Constructor on type 'System.Exception' not found.
edytowany 2x, ostatnio: Gworys
KO
KO
  • Rejestracja:ponad 6 lat
  • Ostatnio:ponad rok
  • Postów:9
0

Nie komplikujmy problemu i pozostańmy przy prostym modelu :)

Dzięki za powyższy link.
Z tego co rozumiem, ValueObject ma nie tylko hermetyzować dostęp do pozycji zamówienia ale zawierać również informację o ilości sztuk dostępnych dla danego produktu?

Kopiuj
class ProductOrder : ValueObject {
    public ProductID ProductID { get; }
    public int Quantity { get; }
    public int AvailableQuantity { get; }
} 

W agregacie Order mam metody za pomocą których dodaję, usuwam lub edytuję pozycje zamówienia.
Na koniec zmiany te muszę utrwalić więc przekazuję instancję agregatu do repo:

Kopiuj
orderRepository.Save(order);

Sama implementacja repo już "wie" na których tabelach robić insert, update a na których delete.

Gworys
  • Rejestracja:około 6 lat
  • Ostatnio:ponad 5 lat
  • Postów:139
0

No, byle byś nie zmieniał nic w tabeli, która należy do innych agregatów.


Unhandled Exception: System.MissingMethodException: Constructor on type 'System.Exception' not found.
edytowany 1x, ostatnio: Gworys
KO
KO
  • Rejestracja:ponad 6 lat
  • Ostatnio:ponad rok
  • Postów:9
0

No dobra, jak teraz powinienem obsłużyć przypadek polegający na usunięciu pozycji zamówienia?
Chodzi mi o całą ścieżkę, począwszy od tego co się dzieje w agregacie Order zakończywszy na tym co robi metoda Save w OrderRepository.

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)