Π ΡΡΠΎΠΌ ΠΏΡΠΎΡΡΠΎΠΌ ΡΡΡΠΎΡΠΈΠ°Π»Π΅ ΠΌΡ ΡΠ΄Π΅Π»Π°Π΅ΠΌ ΠΏΠ°ΡΡ ΠΌΠΈΠΊΡΠΎΡΠ΅ΡΠ²ΠΈΡΠΎΠ² Π½Π° Spring Boot ΠΈ ΠΎΡΠ³Π°Π½ΠΈΠ·ΡΠ΅ΠΌ ΠΌΠ΅ΠΆΠ΄Ρ Π½ΠΈΠΌΠΈ Π²Π·Π°ΠΈΠΌΠΎΠ΄Π΅ΠΉΡΡΠ²ΠΈΠ΅ ΡΠ΅ΡΠ΅Π· ΡΡΠ΅ΠΉΠΌΠ²ΠΎΡΠΊ Axon.
ΠΠΎΠΏΡΡΡΠΈΠΌ Ρ Π½Π°Ρ ΡΠ°ΠΊΠ°Ρ Π·Π°Π΄Π°ΡΠ°.
ΠΡΡΡ ΠΈΡΡΠΎΡΠ½ΠΈΠΊ ΡΠ΄Π΅Π»ΠΎΠΊ Π½Π° ΡΠΎΠ½Π΄ΠΎΠ²ΠΎΠΌ ΡΡΠ½ΠΊΠ΅. ΠΡΠΎΡ ΠΈΡΡΠΎΡΠ½ΠΈΠΊ ΠΏΠ΅ΡΠ΅Π΄Π°Π΅Ρ Π½Π°ΠΌ ΡΠ΄Π΅Π»ΠΊΠΈ ΠΏΠΎ Rest-ΠΈΠ½ΡΠ΅ΡΡΠ΅ΠΉΡΡ.
ΠΠ°ΠΌ Π½Π°Π΄ΠΎ ΡΡΠΈ ΡΠ΄Π΅Π»ΠΊΠΈ ΠΏΠΎΠ»ΡΡΠΈΡΡ, ΡΠΎΡ ΡΠ°Π½ΠΈΡΡ Π² Π±Π°Π·Ρ Π΄Π°Π½Π½ΡΡ ΠΈ ΡΠ΄Π΅Π»Π°ΡΡ ΡΠ΄ΠΎΠ±Π½ΠΎΠ΅ in-memory Ρ ΡΠ°Π½ΠΈΠ»ΠΈΡΠ΅.
ΠΡΠΎ Ρ ΡΠ°Π½ΠΈΠ»ΠΈΡΠ΅ Π΄ΠΎΠ»ΠΆΠ½Ρ Π²ΡΠΏΠΎΠ»Π½ΡΡΡ ΡΠ»Π΅Π΄ΡΡΡΠΈΠ΅ ΡΡΠ½ΠΊΡΠΈΠΈ:
- Π²ΠΎΠ·Π²ΡΠ°ΡΠ°ΡΡ ΡΠΏΠΈΡΠΎΠΊ ΡΡΠ΅ΠΉΠ΄ΠΎΠ²;
- Π²ΠΎΠ·Π²ΡΠ°ΡΠ°ΡΡ ΠΏΠΎΠ»Π½ΡΡ ΠΏΠΎΠ·ΠΈΡΠΈΡ, Ρ.Π΅. ΡΠ°Π±Π»ΠΈΡΡ Β«ΠΈΠ½ΡΡΡΡΠΌΠ΅Π½ΡΒ» β Β«ΡΠ΅ΠΊΡΡΠ΅Π΅ ΠΊΠΎΠ»-Π²ΠΎ Π±ΡΠΌΠ°Π³Β»;
- Π²ΠΎΠ·Π²ΡΠ°ΡΠ°ΡΡ ΠΏΠΎΠ·ΠΈΡΠΈΡ ΠΏΠΎ Π·Π°Π΄Π°Π½Π½ΠΎΠΌΡ ΠΈΠ½ΡΡΡΡΠΌΠ΅Π½ΡΡ.
ΠΠ°ΠΊ ΠΌΡ ΠΏΠΎΠ΄ΠΎΠΉΠ΄Π΅ΠΌ ΠΊ ΡΠ΅ΡΠ΅Π½ΠΈΡ ΡΡΠΎΠΉ Π·Π°Π΄Π°ΡΠΈ?
ΠΠΎ Π·Π°Π²Π΅ΡΠ°ΠΌ ΠΌΠΈΠΊΡΠΎΡΠ΅ΡΠ²ΠΈΡΠ½ΠΎΠΉ ΠΌΠΎΠ΄Ρ, Π½Π°ΠΌ Π½Π°Π΄ΠΎ ΡΠ°Π·Π΄Π΅Π»ΠΈΡΡ Π·Π°Π΄Π°ΡΡ Π½Π° ΡΠΎΡΡΠ°Π²Π»ΡΡΡΠΈΠ΅ ΠΌΠΈΠΊΡΠΎΡΠ΅ΡΠ²ΠΈΡΡ:
- ΠΏΠΎΠ»ΡΡΠ΅Π½ΠΈΠ΅ ΠΏΠΎ Rest-Ρ ΡΠ΄Π΅Π»ΠΊΠΈ;
- ΡΠΎΡ ΡΠ°Π½Π΅Π½ΠΈΠ΅ ΡΠ΄Π΅Π»ΠΊΠΈ Π² Π±Π°Π·Ρ Π΄Π°Π½Π½ΡΡ ;
- in-memory Ρ ΡΠ°Π½ΠΈΠ»ΠΈΡΠ΅ Π΄Π»Ρ ΠΏΡΠ΅Π΄ΡΡΠ°Π²Π»Π΅Π½ΠΈΡ Π΄Π°Π½Π½ΡΡ ΠΏΠΎ ΠΏΠΎΠ·ΠΈΡΠΈΠΈ.
ΠΠ°Π²Π°ΠΉΡΠ΅ Π² ΡΠ°ΠΌΠΊΠ°Ρ ΡΡΠΎΠ³ΠΎ ΡΡΡΠΎΡΠΈΠ°Π»Π° ΡΠ΄Π΅Π»Π°Π΅ΠΌ ΠΏΠ΅ΡΠ²ΡΠΉ ΠΈ ΡΡΠ΅ΡΠΈΠΉ ΡΠ΅ΡΠ²ΠΈΡ, Π² Π²ΡΠΎΡΠΎΠΉ ΠΎΡΡΠ°Π²ΠΈΠΌ Π½Π° Π²ΡΠΎΡΡΡ ΡΠ°ΡΡΡ (Π½Π°ΠΏΠΈΡΠΈΡΠ΅ Π² ΠΊΠΎΠΌΠΌΠ΅Π½ΡΠ°ΡΠΈΡΡ Π΅ΡΠ»ΠΈ ΡΡΠΎ ΠΈΠ½ΡΠ΅ΡΠ΅ΡΠ½ΠΎ).
ΠΡΠ°ΠΊ, Ρ Π½Π°Ρ Π΅ΡΡΡ Π΄Π²Π° ΠΌΠΈΠΊΡΠΎΡΠ΅ΡΠ²ΠΈΡΠ°.
ΠΠ΅ΡΠ²ΡΠΉ ΠΏΠΎΠ»ΡΡΠ°Π΅Ρ Π΄Π°Π½Π½ΡΡ ΠΈΠ·Π²Π½Π΅.
ΠΡΠΎΡΠΎΠΉ ΡΡΠΈ Π΄Π°Π½Π½ΡΠ΅ ΠΎΠ±ΡΠ°Π±Π°ΡΡΠ²Π°Π΅Ρ ΠΈ ΠΎΡΠ²Π΅ΡΠ°Π΅Ρ Π½Π° Π²Ρ ΠΎΠ΄ΡΡΠΈΠ΅ Π·Π°ΠΏΡΠΎΡΡ.
ΠΡ ΠΊΠΎΠ½Π΅ΡΠ½ΠΎ Ρ ΠΎΡΠΈΠΌ ΠΏΠΎΠ»ΡΡΠΈΡΡ Π³ΠΎΡΠΈΠ·ΠΎΠ½ΡΠ°Π»ΡΠ½ΠΎΠ΅ ΠΌΠ°ΡΡΡΠ°Π±ΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅, Π±Π΅Π·ΠΎΡΡΠ°Π½ΠΎΠ²ΠΎΡΠ½ΠΎΠ΅ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ ΠΈ ΠΏΡΠΎΡΠΈΠ΅ Π΄ΠΎΡΡΠΎΠΈΠ½ΡΡΠ²Π° ΠΌΠΈΠΊΡΠΎΡΠ΅ΡΠ²ΠΈΡΠΎΠ².
ΠΠ°ΠΊΠ°Ρ, Π²Π΅ΡΡΠΌΠ° Π½Π΅ΠΏΡΠΎΡΡΠ°Ρ, Π·Π°Π΄Π°ΡΠ° ΠΏΠ΅ΡΠ΅Π΄ Π½Π°ΠΌΠΈ ΡΡΠΎΠΈΡ?
ΠΠΎΠΎΠ±ΡΠ΅-ΡΠΎ ΠΈΡ ΠΌΠ½ΠΎΠ³ΠΎ, Π½ΠΎ ΡΠ΅ΠΉΡΠ°Ρ Π΄Π°Π²Π°ΠΉΡΠ΅ ΠΏΠΎΠ³ΠΎΠ²ΠΎΡΠΈΠΌ, ΠΊΠ°ΠΊ ΠΌΠ΅ΠΆΠ΄Ρ ΡΡΠΈΠΌΠΈ ΠΌΠΈΠΊΡΠΎΡΠ΅ΡΠ²ΠΈΡΠ°ΠΌΠΈ Π±ΡΠ΄ΡΡ ΠΈΠ΄ΡΠΈ Π΄Π°Π½Π½ΡΠ΅. ΠΠ΅ΠΆΠ΄Ρ Π½ΠΈΠΌΠΈ ΠΌΠΎΠΆΠ½ΠΎ ΡΠΎΠΆΠ΅ ΡΠ΄Π΅Π»Π°ΡΡ Rest, ΠΌΠΎΠΆΠ½ΠΎ ΠΏΠΎΡΡΠ°Π²ΠΈΡΡ ΠΊΠ°ΠΊΡΡ-Π½ΠΈΠ±ΡΠ΄Ρ ΠΎΡΠ΅ΡΠ΅Π΄Ρ, ΠΌΠΎΠΆΠ½ΠΎ ΠΌΠ½ΠΎΠ³ΠΎ ΡΠ΅Π³ΠΎ ΠΏΡΠΈΠ΄ΡΠΌΠ°ΡΡ ΡΠΎ ΡΠ²ΠΎΠΈΠΌΠΈ ΠΏΠ»ΡΡΠ°ΠΌΠΈ ΠΈ ΠΌΠΈΠ½ΡΡΠ°ΠΌΠΈ.
ΠΠ°Π²Π°ΠΉΡΠ΅ ΡΠ°ΡΡΠΌΠΎΡΡΠΈΠΌ ΠΎΠ΄ΠΈΠ½ ΠΈΠ· Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΡΡ ΠΏΠΎΠ΄Ρ ΠΎΠ΄ΠΎΠ² β Π°ΡΡΠΈΠ½Ρ ΡΠΎΠ½Π½ΠΎΠ΅ Π²Π·Π°ΠΈΠΌΠΎΠ΄Π΅ΠΉΡΡΠ²ΠΈΠ΅ ΡΠ΅ΡΠ΅Π· Axon-ΡΡΠ΅ΠΉΠΌΠ²ΠΎΡΠΊ.
ΠΠ°ΠΊΠΈΠ΅ ΠΏΠ»ΡΡΡ ΡΠ°ΠΊΠΎΠ³ΠΎ ΡΠ΅ΡΠ΅Π½ΠΈΡ?
ΠΠΎ-ΠΏΠ΅ΡΠ²ΡΡ , Π°ΡΡΠΈΠ½Ρ ΡΠΎΠ½Π½ΠΎΠ΅ Π²Π·Π°ΠΈΠΌΠΎΠ΄Π΅ΠΉΡΡΠ²ΠΈΠ΅ ΡΠ²Π΅Π»ΠΈΡΠΈΠ²Π°Π΅Ρ Π³ΠΈΠ±ΠΊΠΎΡΡΡ (Π΄Π°, Π΅ΡΡΡ ΡΡΡ ΠΈ ΠΌΠΈΠ½ΡΡ, Π½ΠΎ ΠΌΡ ΠΏΠΎΠΊΠ° Π²Π΅Π΄Ρ ΡΠΎΠ»ΡΠΊΠΎ ΠΎ ΠΏΠ»ΡΡΠ°Ρ ).
ΠΠΎ-Π²ΡΠΎΡΡΡ
, ΠΏΡΡΠΌΠΎ ΠΈΠ· ΠΊΠΎΡΠΎΠ±ΠΊΠΈ ΠΌΡ ΠΏΠΎΠ»ΡΡΠ°Π΅ΠΌ Event Sourcing ΠΈ CQRS.
Π-ΡΡΠ΅ΡΡΠΈΡ
, Axon ΠΏΡΠ΅Π΄ΠΎΡΡΠ°Π²Π»ΡΠ΅Ρ Π³ΠΎΡΠΎΠ²ΡΡ ΠΈΠ½ΡΡΠ°ΡΡΡΡΠΊΡΡΡΡ, ΠΈ Π½Π°ΠΌ Π½Π°Π΄ΠΎ ΡΠΎΡΡΠ΅Π΄ΠΎΡΠΎΡΠΈΡΡΡΡ ΡΠΎΠ»ΡΠΊΠΎ Π½Π° ΡΠ°Π·ΡΠ°Π±ΠΎΡΠΊΠ΅ Π±ΠΈΠ·Π½Π΅Ρ-Π»ΠΎΠ³ΠΈΠΊΠΈ.
ΠΡΠΈΡΡΡΠΏΠΈΠΌ.
ΠΡΠΎΠ΅ΠΊΡ Ρ Π½Π°Ρ Π±ΡΠ΄Π΅Ρ Π½Π° gradle. Π Π½Π΅ΠΌ Π±ΡΠ΄Π΅Ρ ΡΡΠΈ ΠΌΠΎΠ΄ΡΠ»Ρ:
- common. ΠΌΠΎΠ΄ΡΠ»Ρ Ρ ΠΎΠ±ΡΠΈΠΌΠΈ ΡΡΡΡΠΊΡΡΡΠ°ΠΌΠΈ Π΄Π°Π½Π½ΡΡ (ΠΌΡ Π½Π΅ Π»ΡΠ±ΠΈΠΌ ΠΊΠΎΠΏΠΈΠΏΠ°ΡΡΡ);
- tradeCreator. ΠΌΠΎΠ΄ΡΠ»Ρ Ρ ΠΌΠΈΠΊΡΠΎΡΠ΅ΡΠ²ΠΈΡΠΎΠΌ Π΄Π»Ρ ΠΏΡΠΈΠ΅ΠΌΠ° ΡΠ΄Π΅Π»ΠΎΠΊ ΠΏΠΎ Rest;
- tradeQueries. ΠΌΠΎΠ΄ΡΠ»Ρ Ρ ΠΌΠΈΠΊΡΠΎΡΠ΅ΡΡΠΈΡΠΎΠΌ Π΄Π»Ρ ΠΎΡΠΎΠ±ΡΠ°ΠΆΠ΅Π½ΠΈΡ ΠΏΠΎΠ·ΠΈΡΠΈΠΈ.
ΠΠΎΠ·ΡΠΌΠ΅ΠΌ Spring Boot Π·Π° ΠΎΡΠ½ΠΎΠ²Ρ ΠΈ ΠΏΠΎΠ΄ΠΊΠ»ΡΡΠΈΠΌ ΡΡΠ°ΡΡΠ΅Ρ Axon-Π°.
Axon ΠΎΡΠ»ΠΈΡΠ½ΠΎ ΡΠ°Π±ΠΎΡΠ°Π΅Ρ ΠΈ Π±Π΅Π· Spring, Π½ΠΎ ΠΌΡ Π±ΡΠ΄Π΅ΠΌ ΠΈΡ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ Π²ΠΌΠ΅ΡΡΠ΅.
Π’ΡΡ Π½Π°Π΄ΠΎ ΠΎΡΡΠ°Π½ΠΎΠ²ΠΈΡΡΡΡ ΠΈ ΠΏΠ°ΡΡ ΡΠ»ΠΎΠ² ΡΠ°ΡΡΠΊΠ°Π·Π°ΡΡ ΠΏΡΠΎ Axon.
ΠΡΠΎ ΠΊΠ»ΠΈΠ΅Π½Ρ-ΡΠ΅ΡΠ²Π΅ΡΠ½Π°Ρ ΡΠΈΡΡΠ΅ΠΌΠ°. ΠΡΡΡ ΡΠ΅ΡΠ²Π΅Ρ β ΡΡΠΎ ΠΎΡΠ΄Π΅Π»ΡΠ½ΠΎ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅, ΠΌΡ Π΅Π³ΠΎ Π±ΡΠ΄Π΅ΠΌ Π·Π°ΠΏΡΡΠΊΠ°ΡΡ Π² Π΄ΠΎΠΊΠ΅ΡΠ΅.
Π Π΅ΡΡΡ ΠΊΠ»ΠΈΠ΅Π½ΡΡ, ΠΊΠΎΡΠΎΡΡΠ΅ Π²ΡΡΡΠ°ΠΈΠ²Π°ΡΡΡΡ Π² ΠΌΠΈΠΊΡΠΎΡΠ΅ΡΠ²ΠΈΡΡ.
ΠΠΎΠ»ΡΡΠ°Π΅ΡΡΡ ΡΠ°ΠΊΠ°Ρ ΠΊΠ°ΡΡΠΈΠ½Π°. Π‘Π½Π°ΡΠ°Π»Π° Π·Π°ΠΏΡΡΠΊΠ°Π΅ΡΡΡ Axon-ΡΠ΅ΡΠ²Π΅Ρ (Π² Π΄ΠΎΠΊΠ΅ΡΠ΅), ΠΏΠΎΡΠΎΠΌ Π½Π°ΡΠΈ ΠΌΠΈΠΊΡΠΎΡΠ΅ΡΠ²ΠΈΡΡ.
ΠΡΠΈ ΡΡΠ°ΡΡΠ΅ ΠΌΠΈΠΊΡΠΎΡΠ΅ΡΠ²ΠΈΡΡ ΠΈΡΡΡ ΡΠ΅ΡΠ²Π΅Ρ ΠΈ Π½Π°ΡΠΈΠ½Π°ΡΡ Ρ Π½ΠΈΠΌ Π²Π·Π°ΠΈΠΌΠΎΠ΄Π΅ΠΉΡΡΠ²ΠΎΠ²Π°ΡΡ. ΠΠ·Π°ΠΈΠΌΠΎΠ΄Π΅ΠΉΡΡΠ²ΠΈΠ΅ ΡΡΠ»ΠΎΠ²Π½ΠΎ ΠΌΠΎΠΆΠ½ΠΎ ΡΠ°Π·Π΄Π΅Π»ΠΈΡΡ Π½Π° Π΄Π²Π° Π²ΠΈΠ΄Π°: ΡΠ΅Ρ Π½ΠΈΡΠ΅ΡΠΊΠΎΠ΅ ΠΈ Π±ΠΈΠ·Π½Π΅ΡΠΎΠ²ΠΎΠ΅.
Π’Π΅Ρ Π½ΠΈΡΠ΅ΡΠΊΠΎΠ΅ β ΡΡΠΎ ΠΎΠ±ΠΌΠ΅Π½ ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΡΠΌΠΈ Β«Ρ ΠΆΠΈΠ²ΠΎΠΉΒ» (ΡΠ°ΠΊΠΈΠ΅ ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΡ ΠΌΠΎΠΆΠ½ΠΎ ΡΠ²ΠΈΠ΄Π΅ΡΡ Π² ΡΠ΅ΠΆΠΈΠΌΠ΅ Π»ΠΎΠ³ΠΈΡΠΎΠ²Π°Π½ΠΈΡ debug).
ΠΠΈΠ·Π½Π΅ΡΠΎΠ²ΠΎΠ΅ β ΡΡΠΎ ΠΎΠ±ΠΌΠ΅Ρ ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΡΠΌΠΈ Π²ΡΠΎΠ΄Π΅ Β«Π½ΠΎΠ²Π°Ρ ΡΠ΄Π΅Π»ΠΊΠ°Β».
ΠΠ°ΠΆΠ½Π°Ρ ΠΎΡΠΎΠ±Π΅Π½Π½ΠΎΡΡΡ, ΠΏΠΎΡΠ»Π΅ Π·Π°ΠΏΡΡΠΊΠ° ΠΌΠΈΠΊΡΠΎΡΠ΅ΡΠ²ΠΈΡ ΠΌΠΎΠΆΠ΅Ρ ΡΠΏΡΠΎΡΠΈΡΡ Ρ Axon-ΡΠ΅ΡΠ²Π΅ΡΠ° Β«ΡΡΠΎ ΠΏΡΠΎΠΈΠ·ΠΎΡΠ»ΠΎΒ» ΠΈ ΡΠ΅ΡΠ²Π΅Ρ ΠΏΠ΅ΡΠ΅Π΄Π°Π΅Ρ ΠΌΠΈΠΊΡΠΎΡΠ΅ΡΠ²ΠΈΡΡ Π½Π°ΠΊΠΎΠΏΠ»Π΅Π½Π½ΡΠ΅ ΡΠΎΠ±ΡΡΠΈΡ. Π’Π°ΠΊΠΈΠΌ ΠΎΠ±ΡΠ°Π·ΠΎΠΌ, ΠΌΠΈΠΊΡΠΎΡΠ΅ΡΠ²ΠΈΡ ΠΎΡΠ½ΠΎΡΠΈΡΠ΅Π»ΡΠ½ΠΎ Π±Π΅Π·ΠΎΠΏΠ°ΡΠ½ΠΎ ΠΌΠΎΠΆΠ΅Ρ Π±ΡΡΡ ΠΏΠ΅ΡΠ΅Π·Π°ΠΏΡΡΠ΅Π½ Π±Π΅Π· ΠΏΠΎΡΠ΅ΡΠΈ Π΄Π°Π½Π½ΡΡ
.
ΠΡΠΈ ΡΠ°ΠΊΠΎΠΉ ΡΡ
Π΅ΠΌΠ΅ ΠΎΠ±ΠΌΠ΅Π½Π° ΠΌΡ ΠΌΠΎΠΆΠ΅ΠΌ ΠΎΡΠ΅Π½Ρ ΠΏΡΠΎΡΡΠΎ Π·Π°ΠΏΡΡΠΊΠ°ΡΡ ΠΌΠ½ΠΎΠ³ΠΎ ΡΠΊΠ·Π΅ΠΌΠΏΠ»ΡΡΠΎΠ² ΠΌΠΈΠΊΡΠΎΡΠ΅ΡΠ²ΠΈΡΠΎΠ²,
ΠΏΡΠΈΡΠ΅ΠΌ Π½Π° ΡΠ°Π·Π½ΡΡ
Ρ
ΠΎΡΡΠ°Ρ
.
ΠΠ°, ΠΎΠ΄ΠΈΠ½ ΡΠΊΠ·Π΅ΠΌΠΏΠ»ΡΡ Axon-ΡΠ΅ΡΠ²Π΅ΡΠ° β ΡΡΠΎ Π½Π΅ Π½Π°Π΄Π΅ΠΆΠ½ΠΎ, Π½ΠΎ ΠΏΠΎΠΊΠ° ΡΠ°ΠΊ.
ΠΡ ΡΠ°Π±ΠΎΡΠ°Π΅ΠΌ Π² ΠΏΠ°ΡΠ°Π΄ΠΈΠ³ΠΌΠ°Ρ Event Sourcing ΠΈ CQRS. ΠΡΠΎ Π·Π½Π°ΡΠΈΡ, ΡΡΠΎ Ρ Π½Π°Ρ Π΄ΠΎΠ»ΠΆΠ½Ρ Π±ΡΡΡ Β«ΠΊΠΎΠΌΠ°Π½Π΄ΡΒ», Β«ΡΠΎΠ±ΡΡΠΈΡΒ» ΠΈ Β«Π²ΡΠ±ΠΎΡΠΊΠΈΒ».
Π£ Π½Π°Ρ Π±ΡΠ΄Π΅Ρ ΠΎΠ΄Π½Π° ΠΊΠΎΠΌΠ°Π½Π΄Π°: Β«ΡΠΎΠ·Π΄Π°ΡΡ ΡΠ΄Π΅Π»ΠΊΡΒ», ΠΎΠ΄Π½ΠΎ ΡΠΎΠ±ΡΡΠΈΠ΅ Β«ΡΠ΄Π΅Π»ΠΊΠ° ΡΠΎΠ·Π΄Π°Π½Π°Β» ΠΈ ΡΡΠΈ Π²ΡΠ±ΠΎΡΠΊΠΈ: Β«ΠΏΠΎΠΊΠ°Π·Π°ΡΡ Π²ΡΠ΅ ΡΠ΄Π΅Π»ΠΊΠΈΒ», Β«ΠΏΠΎΠΊΠ°Π·Π°ΡΡ ΠΏΠΎΠ·ΠΈΡΠΈΡΒ», Β«ΠΏΠΎΠΊΠ°Π·Π°ΡΡ ΠΏΠΎΠ·ΠΈΡΠΈΡ ΠΏΠΎ ΠΈΠ½ΡΡΡΡΠΌΠ΅Π½ΡΡΒ».
Π‘Ρ Π΅ΠΌΠ° ΡΠ°Π±ΠΎΡΡ ΠΏΠΎΠ»ΡΡΠ°Π΅ΡΡΡ ΡΠ°ΠΊΠΎΠΉ:
- ΠΠΈΠΊΡΠΎΡΠ΅ΡΠ²ΠΈΡ tradeCreator ΠΏΡΠΈΠ½ΠΈΠΌΠ°Π΅Ρ ΡΠ΄Π΅Π»ΠΊΡ ΠΏΠΎ Rest.
- ΠΠΈΠΊΡΠΎΡΠ΅ΡΠ²ΠΈΡ tradeCreator ΡΠΎΠ·Π΄Π°Π΅Ρ ΠΊΠΎΠΌΠ°Π½Π΄Ρ Β«ΡΠΎΠ·Π΄Π°ΡΡ ΡΠ΄Π΅Π»ΠΊΡΒ» ΠΈ ΠΎΡΠΏΡΠ°Π²Π»ΡΠ΅Ρ Π΅Π΅ Π² Axon-ΡΠ΅ΡΠ²Π΅Ρ.
- Axon-ΡΠ΅ΡΠ²Π΅Ρ ΠΏΡΠΈΠ½ΠΈΠΌΠ°Π΅Ρ ΠΊΠΎΠΌΠ°Π½Π΄Ρ ΠΈ ΠΏΠ΅ΡΠ΅ΡΡΠ»Π°Π΅Ρ ΠΊΠΎΠΌΠ°Π½Π΄Ρ Π·Π°ΠΈΠ½ΡΠ΅ΡΠ΅ΡΠΎΠ²Π°Π½Π½ΠΎΠΌΡ ΠΏΠΎΠ»ΡΡΠ°ΡΠ΅Π»Ρ, Π² Π½Π°ΡΠ΅ΠΌ ΡΠ»ΡΡΠ°Π΅ ΡΡΠΎ ΠΌΠΈΠΊΡΠΎΡΠ΅ΡΠ²ΠΈΡ tradeCreator.
- ΠΠΈΠΊΡΠΎΡΠ΅ΡΠ²ΠΈΡ tradeCreator ΠΏΠΎΠ»ΡΡΠ°Π΅Ρ ΠΊΠΎΠΌΠ°Π½Π΄Ρ, ΡΠΎΡΠΌΠΈΡΡΠ΅Ρ ΡΠΎΠ±ΡΡΠΈΠ΅ Β«ΡΠ΄Π΅Π»ΠΊΠ° ΡΠΎΠ·Π΄Π°Π½Π°Β» ΠΈ ΠΎΡΠΏΡΠ°Π²Π»ΡΠ΅Ρ Π΅Π³ΠΎ Axon-ΡΠ΅ΡΠ²Π΅ΡΡ.
- Axon-ΡΠ΅ΡΠ²Π΅Ρ ΠΏΡΠΈΠ½ΠΈΠΌΠ°Π΅Ρ ΡΠΎΠ±ΡΡΠΈΠ΅ ΠΈ ΠΏΠ΅ΡΠ΅ΡΡΠ»Π°Π΅Ρ Π·Π°ΠΈΠ½ΡΠ΅ΡΠ΅ΡΠΎΠ²Π°Π½Π½ΡΠΌ ΠΏΠΎΠ΄ΠΏΠΈΡΡΠΈΠΊΠ°ΠΌ.
- Π‘Π΅ΠΉΡΠ°Ρ Ρ Π½Π°Ρ ΡΠΎΠ»ΡΠΊΠΎ ΠΎΠ΄ΠΈΠ½ Π·Π°ΠΈΠ½ΡΠ΅ΡΠ΅ΡΠΎΠ²Π°Π½Π½ΡΠΉ ΠΏΠΎΠ»ΡΡΠ°ΡΠ΅Π»Ρ β ΡΡΠΎ ΠΌΠΈΠΊΡΠΎΡΠ΅ΡΠ²ΠΈΡ tradeQueries.
- ΠΠΈΠΊΡΠΎΡΠ΅ΡΠ²ΠΈΡ tradeQueries ΠΏΠΎΠ»ΡΡΠ°Π΅Ρ ΡΠΎΠ±ΡΡΠΈΠ΅ ΠΈ ΠΎΠ±Π½ΠΎΠ²Π»ΡΠ΅Ρ Π²Π½ΡΡΡΠ΅Π½Π½ΠΈΠ΅ Π΄Π°Π½Π½ΡΠ΅.
(ΠΠ°ΠΆΠ½ΠΎ, ΡΡΠΎ Π² ΠΌΠΎΠΌΠ΅Π½Ρ ΡΠΎΡΠΌΠΈΡΠΎΠ²Π°Π½ΠΈΡ ΡΠΎΠ±ΡΡΠΈΡ ΠΠΈΠΊΡΠΎΡΠ΅ΡΠ²ΠΈΡ tradeQueries ΠΌΠΎΠΆΠ΅Ρ Π±ΡΡΡ Π½Π΅ Π΄ΠΎΡΡΡΠΏΠ΅Π½, Π½ΠΎ ΠΊΠ°ΠΊ ΡΠΎΠ»ΡΠΊΠΎ ΠΎΠ½ Π·Π°ΠΏΡΡΡΠΈΡΡΡ, ΡΠΎ ΡΡΠ°Π·Ρ ΠΏΠΎΠ»ΡΡΠΈΡ ΡΠΎΠ±ΡΡΠΈΠ΅).
ΠΠ°, axon-ΡΠ΅ΡΠ²Π΅Ρ ΡΡΠΎΠΈΡ Π² ΡΠ΅Π½ΡΡΠ΅ ΠΊΠΎΠΌΠΌΡΠ½ΠΈΠΊΡΠΈΠΉ, Π²ΡΠ΅ ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΡ ΠΈΠ΄ΡΡ ΡΠ΅ΡΠ΅Π· Π½Π΅Π³ΠΎ.
ΠΠ°Π²Π°ΠΉΡΠ΅ ΠΏΠ΅ΡΠ΅Ρ ΠΎΠ΄ΠΈΡΡ ΠΊ ΠΊΠΎΠ΄ΠΈΡΠΎΠ²Π°Π½ΠΈΡ.
Π§ΡΠΎΠ±Ρ Π½Π΅ Π·Π°Π³ΡΠΎΠΌΠΎΠΆΠ΄Π°ΡΡ ΠΏΠΎΡΡ ΠΊΠΎΠ΄ΠΎΠΌ, Π½ΠΈΠΆΠ΅ Ρ ΠΏΡΠΈΠ²Π΅Π΄Ρ ΡΠΎΠ»ΡΠΊΠΎ ΡΡΠ°Π³ΠΌΠ΅Π½ΡΡ, ΡΡΡΠ»ΠΊΠ° Π½Π° ΠΏΡΠΈΠΌΠ΅Ρ ΡΠ΅Π»ΠΈΠΊΠΎΠΌ Π±ΡΠ΄Π΅Ρ Π½ΠΈΠΆΠ΅.
ΠΠ°ΡΠ½Π΅ΠΌ Ρ ΠΎΠ±ΡΠ΅Π³ΠΎ ΠΌΠΎΠ΄ΡΠ»Ρ common.
Π Π½Π΅ΠΌ ΠΎΠ±ΡΠΈΠ΅ ΡΠ°ΡΡΠΈ β ΡΡΠΎ ΡΠΎΠ±ΡΡΠΈΠ΅ (class CreatedTradeEvent). ΠΠ±ΡΠ°ΡΠΈΡΠ΅ Π²Π½ΠΈΠΌΠ°Π½ΠΈΠ΅ Π½Π° Π½Π°ΠΈΠΌΠ΅Π½ΠΎΠ²Π°Π½ΠΈΠ΅, ΠΏΠΎ ΡΡΡΠΈ, ΡΡΠΎ Π½Π°Π·Π²Π°Π½ΠΈΠ΅ ΠΊΠΎΠΌΠ°Π½Π΄Ρ, ΠΊΠΎΡΠΎΡΠ°Ρ ΠΏΠΎΡΠΎΠ΄ΠΈΠ»Π° ΡΡΠΎ ΡΠΎΠ±ΡΡΠΈΠ΅, Π½ΠΎ Π² ΠΏΡΠΎΡΠ΅Π΄ΡΠ΅ΠΌ Π²ΡΠ΅ΠΌΠ΅Π½ΠΈ. Π ΠΏΡΠΎΡΠ΅Π΄ΡΠ΅ΠΌ, Ρ.ΠΊ. ΡΠ½Π°ΡΠ°Π»Π° ΠΏΠΎΡΠ²Π»ΡΠ΅ΡΡΡ ΠΊΠΎΠΌΠ°Π½Π΄Π°, ΠΊΠΎΡΠΎΡΠ°Ρ ΠΏΡΠΈΠ²ΠΎΠ΄ΠΈΡ ΠΊ ΡΠΎΠ·Π΄Π°Π½ΠΈΡ ΡΠΎΠ±ΡΡΠΈΡ.
Π Π΄ΡΡΠ³ΠΈΠΌ ΠΎΠ±ΡΠΈΠΌ ΡΡΡΡΠΊΡΡΡΠ°ΠΌ ΠΎΡΠ½ΠΎΡΡΡΡΡ ΠΊΠ»Π°ΡΡΡ Π΄Π»Ρ ΠΎΠΏΠΈΡΠ°Π½ΠΈΡ ΠΏΠΎΠ·ΠΈΡΠΈΠΈ (class Position), ΡΠ΄Π΅Π»ΠΊΠΈ (class Trade) ΠΈ ΡΡΠΎΡΠΎΠ½Π° ΡΠ΄Π΅Π»ΠΊΠΈ (enum Side), Ρ.Π΅. ΠΊΡΠΏΠ»Ρ ΠΈΠ»ΠΈ ΠΏΡΠΎΠ΄Π°ΠΆΠ°.
ΠΠ΅ΡΠ΅Ρ ΠΎΠ΄ΠΈΠΌ ΠΊ ΠΌΠΎΠ΄ΡΠ»Ρ tradeCreator.
ΠΡΠΎΡ ΠΌΠΎΠ΄ΡΠ»Ρ ΠΈΠΌΠ΅Π΅Ρ Rest-ΠΈΠ½ΡΠ΅ΡΡΠ΅ΠΉΡ (class TradeController) Π΄Π»Ρ ΠΏΡΠΈΠ΅ΠΌΠ° ΡΠ΄Π΅Π»ΠΎΠΊ.
ΠΠ· ΠΏΠΎΠ»ΡΡΠ΅Π½Π½ΠΎΠΉ ΡΠ΄Π΅Π»ΠΊΠΈ ΡΠΎΡΠΌΠΈΡΡΠ΅ΡΡΡ ΠΊΠΎΠΌΠ°Π½Π΄Π° Β«ΡΠΎΠ·Π΄Π°ΡΡ ΡΠ΄Π΅Π»ΠΊΡΒ» ΠΈ ΠΎΡΠΏΡΠ°Π²Π»ΡΠ΅ΡΡΡ Π² axon-ΡΠ΅ΡΠ²Π΅Ρ.
@PostMapping("/trade")
public ResponseEntity<String> create(@RequestBody Trade trade) {
var createTradeCommand = CreateTradeCommand.builder()
.tradeId(trade.getTradeId())
...
.build();
var result = commandGateway.sendAndWait(createTradeCommand, 3, TimeUnit.SECONDS);
return ResponseEntity.ok(result.get().toString());
}
ΠΠ»Ρ ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΠΈ ΠΊΠΎΠΌΠ°Π½Π΄Ρ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΡΡΡ ΠΊΠ»Π°ΡΡ class TradeAggregate.
Π§ΡΠΎΠ±Ρ Axon Π΅Π³ΠΎ Π½Π°ΡΠ΅Π», ΡΡΠ°Π²ΠΈΠΌ Π°Π½Π½ΠΎΡΠ°ΡΠΈΡ @Aggregate.
ΠΠ΅ΡΠΎΠ΄ Π΄Π»Ρ ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΠΈ ΠΊΠΎΠΌΠ°Π½Π΄Ρ Π²ΡΠ³Π»ΡΠ΄ΠΈΡ ΡΠ°ΠΊ (Ρ ΡΠΎΠΊΡΠ°ΡΠ΅Π½ΠΈΠ΅ΠΌ):
@CommandHandler
public TradeAggregate(CreateTradeCommand command) {
log.info("command: {}", command);
var event = CreatedTradeEvent.builder()
.tradeId(command.tradeId())
....
.build();
AggregateLifecycle.apply(event);
}
ΠΠ· ΠΊΠΎΠΌΠ°Π½Π΄Ρ ΡΠΎΡΠΌΠΈΡΡΠ΅ΡΡΡ ΡΠΎΠ±ΡΡΠΈΠ΅ ΠΈ ΠΎΡΠΏΡΠ°Π²Π»ΡΠ΅ΡΡΡ Π½Π° ΡΠ΅ΡΠ²Π΅Ρ.
ΠΠΎΠΌΠ°Π½Π΄Π° Π½Π°Ρ
ΠΎΠ΄ΠΈΡΡΡ Π² ΠΊΠ»Π°ΡΡΠ΅ CreateTradeCommand.
Π’Π΅ΠΏΠ΅ΡΡ ΠΏΠΎΡΠΌΠΎΡΡΠΈΠΌ Π½Π° ΠΏΠΎΡΠ»Π΅Π΄Π½ΠΈΠΉ ΠΌΠΎΠ΄ΡΠ»Ρ tradeQueries.
ΠΡΠ±ΠΎΡΠΊΠΈ ΠΎΠΏΠΈΡΡΠ²Π°ΡΡΡΡ Π² ΠΏΠ°ΠΊΠ΅ΡΠ΅ queries.
Π ΡΡΠΎΠΌ ΠΌΠΎΠ΄ΡΠ»Π΅ ΡΠΎΠΆΠ΅ Π΅ΡΡΡ Rest-ΠΈΠ½ΡΠ΅ΡΡΠ΅ΠΉΡ
public class TradeController.
ΠΠ»Ρ ΠΏΡΠΈΠΌΠ΅ΡΠ° ΠΏΠΎΡΠΌΠΎΡΡΠΈΠΌ ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΡ Π·Π°ΠΏΡΠΎΡΠ°: Β«ΠΏΠΎΠΊΠ°Π·Π°ΡΡ Π²ΡΠ΅ ΡΠ΄Π΅Π»ΠΊΠΈΒ».
@GetMapping("/trade/all")
public List<Trade> findAllTrades() {
return queryGateway.query(new FindAllTradesQuery(),
ResponseTypes.multipleInstancesOf(Trade.class)).join();
}
Π‘ΠΎΠ·Π΄Π°Π΅ΡΡΡ Π·Π°ΠΏΡΠΎΡ Π½Π° Π²ΡΠ±ΠΎΡΠΊΡ ΠΈ ΠΎΡΠΏΡΠ°Π²Π»ΡΠ΅ΡΡΡ Π½Π° ΡΠ΅ΡΠ²Π΅Ρ.
ΠΠ»Ρ ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΠΈ Π·Π°ΠΏΡΠΎΡΠ° Π½Π° Π²ΡΠ±ΠΎΡΠΊΡ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΡΡΡ ΠΊΠ»Π°ΡΡ TradesEventHandler.
Π Π½Π΅ΠΌ Π΅ΡΡΡ ΠΌΠ΅ΡΠΎΠ΄, ΠΎΡΠΌΠ΅ΡΠ΅Π½Π½ΡΠΉ Π°Π½Π½ΠΎΡΠ°ΡΠΈΠ΅ΠΉ
@QueryHandler
public List<Position> handleFindCurrentPositionQuery(FindCurrentPositionQuery query)
ΠΠΌΠ΅Π½Π½ΠΎ ΠΎΠ½ ΠΈ ΠΎΡΠ²Π΅ΡΠ°Π΅Ρ Π·Π° Π²ΡΠ±ΠΎΡΠΊΡ Π΄Π°Π½Π½ΡΡ ΠΈΠ· in-memory Ρ ΡΠ°Π½ΠΈΠ»ΠΈΡΠ°.
ΠΠΎΠ·Π½ΠΈΠΊΠ°Π΅Ρ Π²ΠΎΠΏΡΠΎΡ, ΠΊΠ°ΠΊ Π² ΡΡΠΎΠΌ Ρ ΡΠ°Π½ΠΈΠ»ΠΈΡΠ΅ ΠΎΠ±Π½ΠΎΠ²Π»ΡΠ΅ΡΡΡ ΠΈΠ½ΡΠΎΡΠΌΠ°ΡΠΈΡ.
ΠΠ°ΡΠ½Π΅ΠΌ Ρ ΡΠΎΠ³ΠΎ, ΡΡΠΎ ΡΡΠΎ ΠΏΡΠΎΡΡΠΎ Π½Π°Π±ΠΎΡ ConcurrentHashMap, Π·Π°ΡΠΎΡΠ΅Π½Π½ΡΡ
ΠΏΠΎΠ΄ ΠΊΠΎΠ½ΠΊΡΠ΅ΡΠ½ΡΠ΅ Π²ΡΠ±ΠΎΡΠΊΠΈ.
ΠΠ»Ρ ΠΈΡ
ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΡ ΠΏΡΠΈΠΌΠ΅Π½ΡΠ΅ΡΡΡ ΠΌΠ΅ΡΠΎΠ΄:
@EventHandler
public void on(CreatedTradeEvent event) {
log.info("event:{}", event);
var trade = Trade.builder()
...
.build();
trades.put(event.tradeId(), trade);
position.merge(event.shortName(), event.size(),
(oldValue, value) -> event.side() == Side.BUY ? oldValue + value : oldValue - value);
}
ΠΠ½ ΠΏΡΠΈΠ½ΠΈΠΌΠ°Π΅Ρ ΡΠΎΠ±ΡΡΠΈΠ΅ Β«ΡΠ΄Π΅Π»ΠΊΠ° ΡΠΎΠ·Π΄Π°Π½Π°Β» ΠΈ ΠΎΠ±Π½ΠΎΠ²Π»ΡΠ΅Ρ Map-Ρ.
ΠΡΠΎ ΠΎΡΠ½ΠΎΠ²Π½ΡΠ΅ ΠΌΠΎΠΌΠ΅Π½ΡΡ ΡΠ°Π·ΡΠ°Π±ΠΎΡΠΊΠΈ ΠΌΠΈΠΊΡΠΎΡΠ΅ΡΠ²ΠΈΡΠΎΠ².
Π§ΡΠΎ ΠΌΠΎΠΆΠ½ΠΎ ΡΠΊΠ°Π·Π°ΡΡ ΠΏΡΠΎ Π½Π΅Π΄ΠΎΡΡΠ°ΡΠΊΠΈ Axon?
ΠΠΎ-ΠΏΠ΅ΡΠ²ΡΡ , ΡΡΠΎ ΡΡΠ»ΠΎΠΆΠ½Π΅Π½ΠΈΠ΅ ΠΈΠ½ΡΡΠ°ΡΡΡΡΠΊΡΡΡΡ, ΠΏΠΎΡΠ²ΠΈΠ»Π°ΡΡ ΡΠΎΡΠΊΠ° ΠΎΡΠΊΠ°Π·Π° β Axon-ΡΠ΅ΡΠ²Π΅Ρ, Π²ΡΠ΅ ΠΊΠΎΠΌΠΌΡΠ½ΠΈΠΊΠ°ΡΠΈΠΈ ΠΈΠ΄ΡΡ ΡΠ΅ΡΠ΅Π· Π½Π΅Π³ΠΎ.
ΠΠΎ-Π²ΡΠΎΡΡΡ , Π²Π΅ΡΡΠΌΠ° ΡΡΠΊΠΎ ΠΏΡΠΎΡΠ²Π»ΡΠ΅ΡΡΡ Π½Π΅Π΄ΠΎΡΡΠ°ΡΠΎΠΊ ΠΏΠΎΠ΄ΠΎΠ±Π½ΡΡ ΡΠ°ΡΠΏΡΠ΅Π΄Π΅Π»Π΅Π½Π½ΡΡ ΡΠΈΡΡΠ΅ΠΌΡ β Π²ΡΠ΅ΠΌΠ΅Π½Π½Π°Ρ Π½Π΅ΡΠΎΠ³Π»Π°ΡΠΎΠ²Π°Π½Π½ΠΎΡΡΡ Π΄Π°Π½Π½ΡΡ . Π Π½Π°ΡΠ΅ΠΌ ΡΠ»ΡΡΠ°Π΅ ΠΌΠ΅ΠΆΠ΄Ρ ΠΏΠΎΠ»ΡΡΠ΅Π½ΠΈΠ΅ΠΌ Π½ΠΎΠ²ΠΎΠΉ ΡΠ΄Π΅Π»ΠΊΠΈ ΠΈ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ΠΌ Π΄Π°Π½Π½ΡΡ Π΄Π»Ρ Π²ΡΠ±ΠΎΡΠΎΠΊ ΠΌΠΎΠΆΠ΅Ρ ΠΏΡΠΎΠΉΡΠΈ Π½Π΅Π΄ΠΎΠΏΡΡΡΠΈΠΌΠΎ ΠΌΠ½ΠΎΠ³ΠΎ Π²ΡΠ΅ΠΌΠ΅Π½ΠΈ.
Π§ΡΠΎ ΠΎΡΡΠ°Π»ΠΎΡΡ Π·Π° ΠΊΠ°Π΄ΡΠΎΠΌ?
Π‘ΠΎΠ²ΡΠ΅ΠΌ Π½ΠΈΡΠ΅Π³ΠΎ Π½Π΅ ΡΠΊΠ°Π·Π°Π½ΠΎ ΠΏΡΠΎ Event Sourcing ΠΈ CQRS, ΡΡΠΎ ΡΡΠΎ ΡΠ°ΠΊΠΎΠ΅ ΠΈ Π΄Π»Ρ ΡΠ΅Π³ΠΎ Π½ΡΠΆΠ½ΠΎ.
ΠΠ΅Π· ΡΠ°ΡΠΊΡΡΡΠΈΡ ΡΡΠΈΡ
ΠΏΠΎΠ½ΡΡΠΈΠΉ Π½Π΅ΠΊΠΎΡΠΎΡΡΠ΅ ΠΌΠΎΠΌΠ΅Π½ΡΡ ΠΌΠΎΠ³Π»ΠΈ Π±ΡΡΡ Π½Π΅ ΠΏΠΎΠ½ΡΡΠ½Ρ.
ΠΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎ, ΠΎΡΠ΄Π΅Π»ΡΠ½ΡΠ΅ ΡΡΠ°Π³ΠΌΠ΅Π½ΡΡ ΠΊΠΎΠ΄Π° ΡΠΎΠΆΠ΅ ΡΡΠ΅Π±ΡΡΡ ΠΏΠΎΡΡΠ½Π΅Π½ΠΈΠ΅.
ΠΠ± ΡΡΠΎΠΌ ΠΌΡ ΠΏΠΎΠ³ΠΎΠ²ΠΎΡΠΈΠΌ Π½Π°
ΠΡΡΠΎΡΠ½ΠΈΠΊ: habr.com