<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[lsdev.pl]]></title><description><![CDATA[The only impossible journey is the one you never begin]]></description><link>https://lsdev.pl/</link><image><url>https://lsdev.pl/favicon.png</url><title>lsdev.pl</title><link>https://lsdev.pl/</link></image><generator>Ghost 5.89</generator><lastBuildDate>Sun, 05 Apr 2026 13:36:08 GMT</lastBuildDate><atom:link href="https://lsdev.pl/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Strategie zarządzania kodem]]></title><description><![CDATA[<p><a href="https://git-scm.com/?ref=lsdev.pl">Git</a> jako narz&#x119;dzie rozproszonego systemu kontroli wersji jest obecnie standardem u&#x142;atwiaj&#x105;cym zespo&#x142;om programistycznym rozw&#xF3;j oprogramowania, a jego znajomo&#x15B;&#x107; jest w zasadzie niezb&#x119;dna aby by&#x107; efektywnym developerem. Skuteczne zarz&#x105;dzanie kodem przy u&#x17C;yciu</p>]]></description><link>https://lsdev.pl/posts/strategie-zarzadzania-kodem/</link><guid isPermaLink="false">65d3d6c22b45b18f866eb7e3</guid><category><![CDATA[Software]]></category><category><![CDATA[Programming]]></category><dc:creator><![CDATA[Łukasz]]></dc:creator><pubDate>Tue, 20 Feb 2024 01:47:59 GMT</pubDate><media:content url="https://lsdev.pl/content/images/2024/02/compare-fibre-INNsF0Zz_kQ-unsplash.webp" medium="image"/><content:encoded><![CDATA[<img src="https://lsdev.pl/content/images/2024/02/compare-fibre-INNsF0Zz_kQ-unsplash.webp" alt="Strategie zarz&#x105;dzania kodem"><p><a href="https://git-scm.com/?ref=lsdev.pl">Git</a> jako narz&#x119;dzie rozproszonego systemu kontroli wersji jest obecnie standardem u&#x142;atwiaj&#x105;cym zespo&#x142;om programistycznym rozw&#xF3;j oprogramowania, a jego znajomo&#x15B;&#x107; jest w zasadzie niezb&#x119;dna aby by&#x107; efektywnym developerem. Skuteczne zarz&#x105;dzanie kodem przy u&#x17C;yciu tego narz&#x119;dzia przyczynia si&#x119; do zwi&#x119;kszenia jako&#x15B;ci, wydajno&#x15B;ci i stabilno&#x15B;ci projekt&#xF3;w programistycznych z wielu powod&#xF3;w:</p><ul><li><strong>&#x15B;ledzenie zmian</strong> - mo&#x17C;emy &#x142;atwo przejrze&#x107; histori&#x119; projektu, zrozumie&#x107; kto, kiedy i dlaczego wprowadzi&#x142; okre&#x15B;lon&#x105; zmian&#x119;</li><li><strong>wsp&#xF3;&#x142;praca</strong> - wielu programist&#xF3;w mo&#x17C;e jednocze&#x15B;nie pracowa&#x107; nad projektem niezale&#x17C;nie</li><li><strong>zarz&#x105;dzanie wersjami </strong>- utrzymanie wielu r&#xF3;&#x17C;nych wersji oprogramowania</li><li><strong>&#x142;atwe wycofywanie zmian </strong>- &#x142;atwe cofanie si&#x119; do poprzednich stan&#xF3;w projektu</li><li><strong>bezpiecze&#x144;stwo</strong> przed utrat&#x105; danych</li></ul><p>W tym artykule przybli&#x17C;&#x119; trzy najpopularniejsze strategie zarz&#x105;dzania kodem:<br><em>GIT Flow</em>, <em>GitHub Flow</em> i <em>Trunk Based Development</em>.</p><h2 id="git-flow">GIT Flow</h2><figure class="kg-card kg-image-card"><img src="https://lsdev.pl/content/images/2024/02/s2fH9.webp" class="kg-image" alt="Strategie zarz&#x105;dzania kodem" loading="lazy" width="659" height="443" srcset="https://lsdev.pl/content/images/size/w600/2024/02/s2fH9.webp 600w, https://lsdev.pl/content/images/2024/02/s2fH9.webp 659w"></figure><p>Podej&#x15B;cie zak&#x142;ada, &#x17C;e pracujemy na wielu branchach:</p><ul><li>main (lub master) - powinien by&#x107; zgodny z kodem, kt&#xF3;ry jest na produkcji</li><li>develop - kopia brancha main z wszystkimi innymi zmianami, kt&#xF3;re zosta&#x142;y dodane od ostatniego release</li><li>feature - developerzy odbijaj&#x105; si&#x119; od brancha develop tworz&#x105;c brancha feature np. <em>feature/new-auth-method</em> lub <em>bug/fix-some-bug</em> w zale&#x17C;no&#x15B;ci od rodzaju zg&#x142;oszenia</li><li>release - jak developerzy wykonali swoj&#x105; prac&#x119; nowy branch jest tworzony od brancha develop aby stworzy&#x107; release np. <em>release/1.2.3</em></li><li>hotfix - je&#x17C;eli jest pilna potrzeba poprawienia buga na produkcji tworzymy brancha hotfix odbijaj&#x105;c si&#x119; od main np. <em>hotfix/INC3458-xyz</em></li></ul><p><strong>Flow programisty:</strong></p><ol><li>Dla nowych projekt&#xF3;w tworzony jest branch main (lub master)</li><li>Nast&#x119;pnie branch develop jest odbijany od brancha main. Nie dokonujemy &#x17C;adnych zmian bezpo&#x15B;rednio na branchu main czy te&#x17C; develop.</li><li>Programi&#x15B;ci odbijaj&#x105; si&#x119; od brancha develop tworz&#x105;c feature branche i pracuj&#x105; nad swoimi zmianami. Czasem zajdzie potrzeba merga develop -&gt; feature je&#x15B;li implementacja feature d&#x142;ugo trwa i trzeba zintegrowa&#x107; si&#x119; z innymi zmianami (rekomendowanym podej&#x15B;ciem jest u&#x17C;ycie rebase).</li><li>Gdy feature jest gotowy tworzymy pull requesta do developa. Inni cz&#x142;onkowie zespo&#x142;u robi&#x105; code review zmian. Je&#x17C;eli s&#x105; konflikty najpierw trzeba je rozwi&#x105;za&#x107; zanim b&#x119;dziemy mogli zmergowa&#x107; zmiany do developa. Warto aby w ramach danego PR uruchamia&#x142; si&#x119; jaki&#x15B; proces automatyczny, kt&#xF3;ry zweryfikuje czy nic nie popsuli&#x15B;my czy testy przechodz&#x105; itd.</li><li>Jak ju&#x17C; wszystkie zmiany pod danego release zostan&#x105; wykonane release branch jest tworzony odbijaj&#x105;c si&#x119; od brancha develop. QA Team wykonuje testy tego release przed wdro&#x17C;eniem na produkcje. W mi&#x119;dzyczasie developerzy mog&#x105; kontynuowa&#x107; prace na developie je&#x17C;eli chodzi o funkcjonalno&#x15B;ci pod kolejny release.</li><li>Gdy jeste&#x15B;my pewni, &#x17C;e release zosta&#x142; w pe&#x142;ni przetestowany tagujemy wydanie odpowiedni&#x105; wersj&#x105; i mergujemy release do main (master).</li><li>Po wydaniu wersji mergujemy main do develop aby upewni&#x107; si&#x119;, &#x17C;e wszystkie zmiany wykonywane podczas test&#xF3;w s&#x105; uwzgl&#x119;dnione i sp&#xF3;jne z branchem develop.</li><li>Je&#x17C;eli wyst&#x105;pi pilny problem na produkcji tworzymy brancha hotfix naprawiamy wystawiamy PR nast&#x119;pnie po code review mergujemy do main i develop i tagujemy wersje.</li></ol><p><strong>Dlaczego warto odbija&#x107; si&#x119; od mastera?</strong></p><p>Odbicie feature brancha od mastera pozwala nam na wdro&#x17C;enie danego feature w oderwaniu od release&apos;a. W momencie jak biznes si&#x119; decyduje, &#x17C;e dany feature ma nie wej&#x15B;&#x107; unikamy wielu revert&apos;ow. W takim przypadku tworzymy nowego release brancha z mastera i mergujemy wszystkie tylko interesuj&#x105;ce nas feature branche. W takim podej&#x15B;ciu wa&#x17C;ne jest aby nie usuwa&#x107; feature branchy od razu po mergu do release&apos;a tylko w momencie wdro&#x17C;enia zmian na produkcj&#x119;. Wszystkie poprawki dotycz&#x105;ce funkcjonalno&#x15B;ci powinny trafia&#x107; na odpowiedni feature branch (nie powinno by&#x107; commit&apos;ow bezpo&#x15B;rednio do developa, release czy mastera).</p><p><strong>Zalety:</strong></p><ul><li>pozwala pracowa&#x107; na wielu release&apos;ach jednocze&#x15B;nie </li><li>&#x142;atwo &#x15B;ledzi&#x107; poszczeg&#xF3;lne wydania gdy&#x17C; jest wiele branchy i ka&#x17C;dy ma swoje przeznaczenie</li><li>pozwala na &#x142;atwe prze&#x142;&#x105;czanie si&#x119; pomi&#x119;dzy tym nad czym pracujemy a innymi wydaniami poniewa&#x17C; &quot;&#x17C;yj&#x105;&quot; one w r&#xF3;&#x17C;nych branchach</li><li>sprawdza si&#x119; zwykle gdy rzadko wdra&#x17C;amy zmiany na produkcje np. raz na kilka tygodni lub rzadziej</li><li>sprawdza si&#x119; gdy musimy wspiera&#x107; wiele r&#xF3;&#x17C;nych wersji i robi&#x107; fixy do tych wersji oraz zachowa&#x107; kompatybilno&#x15B;&#x107; wsteczn&#x105;</li></ul><p><strong>Wady:</strong></p><ul><li>s&#x142;abo si&#x119; sprawdza w kontek&#x15B;cie CI/CD</li><li>wiele branchy, kt&#xF3;re trzeba utrzymywa&#x107; i pami&#x119;ta&#x107; aby zachowywa&#x142;y sp&#xF3;jno&#x15B;&#x107; mergowa&#x107; itd, mija sporo czasu zanim zmiany zostan&#x105; zmergowane do mastera i jeszcze wi&#x119;cej czasu zanim inni developerzy lub zespo&#x142;y zintegruj&#x105; swoje zmiany z zmianami innych nie wspominaj&#x105;c o konfliktach po drodze</li><li>z uwagi na to, &#x17C;e wydanie jest do&#x15B;&#x107; z&#x142;o&#x17C;one d&#x142;ug technologiczny ro&#x15B;nie</li></ul><h2 id="github-flow">GitHub Flow</h2><figure class="kg-card kg-image-card"><img src="https://lsdev.pl/content/images/2024/02/github-flow.webp" class="kg-image" alt="Strategie zarz&#x105;dzania kodem" loading="lazy" width="1080" height="536" srcset="https://lsdev.pl/content/images/size/w600/2024/02/github-flow.webp 600w, https://lsdev.pl/content/images/size/w1000/2024/02/github-flow.webp 1000w, https://lsdev.pl/content/images/2024/02/github-flow.webp 1080w" sizes="(min-width: 720px) 720px"></figure><p>Strategia u&#x17C;ywana przez Githuba. Podej&#x15B;cie zak&#x142;ada, &#x17C;e mamy tylko dwa branche:</p><ul><li>main (lub master) - podobnie jak w git flow, branch posiada wszystkie stabilne zmiany projektu</li><li>feature - programi&#x15B;ci odbijaj&#x105; si&#x119; bezpo&#x15B;rednio od main aby pracowa&#x107; nad nowymi funkcjonalno&#x15B;ciami</li></ul><p><strong>Flow programisty:</strong></p><ol><li>W przypadku nowego projektu tworzymy pusty branch master.</li><li>Aby wprowadzi&#x107; zmian&#x119; odbijamy si&#x119; od brancha master i tworzymy feature branch.</li><li>Gdy branch feature jest gotowy, przetestowany, a code review zrobione mergujemy do mastera.</li><li>Gdy branch feature zosta&#x142; zmergowany do mastera powinien by&#x107; od razu zrobiony release zmian na produkcje.</li></ol><p>W przeciwie&#x144;stwie do Git Flow nie ma tu koncepcji brancha release. Ka&#x17C;dy feature tworzy sw&#xF3;j w&#x142;asny &quot;release&quot; w zwi&#x105;zku z tym wszystko zmergowane do mastera powinno tworzy&#x107; stan stabilny i deployowalny. Dzi&#x119;ki temu zespo&#x142;y mog&#x105; wdra&#x17C;a&#x107; zmiany na produkcje kilka razy dziennie zamiast na koniec sprintu. <strong>W celu tak szybkiego wdra&#x17C;ania zmian i robienia wyda&#x144; nale&#x17C;y mie&#x107; solidne testy automatyczne.</strong></p><p><strong>Zalety:</strong></p><ul><li>pozwala na wdro&#x17C;enie CI/CD</li><li>zach&#x119;ca zespo&#x142;y do wdra&#x17C;ania cz&#x119;sto dzi&#x119;ki czemu szybko mog&#x105; uzyska&#x107; feedback z pracy, kt&#xF3;r&#x105; wykonali</li><li>mniejsza szansa na wytworzenie d&#x142;ugu technicznego</li></ul><p><strong>Wady:</strong></p><ul><li>wymaga solidnych test&#xF3;w automatycznych i automatycznego procesu release&apos;owego</li><li>trzeba by&#x107; ostro&#x17C;nym poniewa&#x17C; ka&#x17C;dy b&#x142;&#x105;d zmergowany do mastera p&#xF3;jdzie bezpo&#x15B;rednio na produkcje</li></ul><h2 id="trunk-based-development">Trunk Based Development</h2><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lsdev.pl/content/images/2024/02/trunk1b.webp" class="kg-image" alt="Strategie zarz&#x105;dzania kodem" loading="lazy" width="728" height="329" srcset="https://lsdev.pl/content/images/size/w600/2024/02/trunk1b.webp 600w, https://lsdev.pl/content/images/2024/02/trunk1b.webp 728w" sizes="(min-width: 720px) 720px"><figcaption>Trunk-Based Development For Smaller Teams</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lsdev.pl/content/images/2024/02/trunk1c.webp" class="kg-image" alt="Strategie zarz&#x105;dzania kodem" loading="lazy" width="726" height="366" srcset="https://lsdev.pl/content/images/size/w600/2024/02/trunk1c.webp 600w, https://lsdev.pl/content/images/2024/02/trunk1c.webp 726w" sizes="(min-width: 720px) 720px"><figcaption>Scaled Trunk-Based Development</figcaption></figure><p>Trunk-Based development to praktyka, w kt&#xF3;rej developerzy merguj&#x105; cz&#x119;sto ma&#x142;e zmiany do jednego core&apos;owego brancha <em>trunk </em>lub<em> main (single source-of-truth) </em>zamiast utrzymywa&#x107; d&#x142;ugotrwa&#x142;e ga&#x142;&#x119;zie. Pomaga to sprawnie wdra&#x17C;a&#x107; zmiany i osi&#x105;ga&#x107; CI/CD.</p><p>W przeciwie&#x144;stwie do innych strategii, kt&#xF3;re u&#x17C;ywaj&#x105; oddzielnych branchy do release&apos;owania zmian w TBD <em>trunk</em> jest tzw. jednym &#x17A;r&#xF3;d&#x142;em prawdy wszystkich release&apos;ow na produkcje.</p><p>W TBD teoretycznie ka&#x17C;dy commit nadaje si&#x119; do wdro&#x17C;enia na produkcj&#x119;. Ta idea pozwala na bardziej elastyczne i cz&#x119;stsze wdro&#x17C;enia zmian na produkcj&#x119; i skr&#xF3;cenie <em>time-to-market</em>.</p><p>Stosuj&#x105;c TBD zmiany s&#x105; wdra&#x17C;ane na bie&#x17C;&#x105;co, co minimalizuje czas w jakim kod r&#xF3;&#x17C;nych programist&#xF3;w jest oddzielony. Wdro&#x17C;enia s&#x105; cz&#x119;stsze, &#xA0;co u&#x142;atwia &#x15B;ledzenie b&#x142;&#x119;d&#xF3;w i umo&#x17C;liwia dostarczanie funkcji szybciej.</p><p>W celu zastosowania TBD, zespo&#x142;y programistyczne powinny cz&#x119;sto &#x142;&#x105;czy&#x107; swoje zmiany z ga&#x142;&#x119;zi&#x105; g&#x142;&#xF3;wn&#x105;. Wprowadza to zasady takie jak <strong>Feature Flags</strong>, kt&#xF3;re pozwalaj&#x105; na tymczasowe wy&#x142;&#x105;czanie kodu, co umo&#x17C;liwia &#x142;&#x105;czenie r&#xF3;&#x17C;nych funkcji niezale&#x17C;nie od ich gotowo&#x15B;ci do wdro&#x17C;enia. Niezb&#x119;dne jest r&#xF3;wnie&#x17C; stosowanie praktyk takich jak automatyczne testy aby zapewni&#x107;, &#x17C;e &#x142;&#x105;czone zmiany nie wprowadzaj&#x105; nowych b&#x142;&#x119;d&#xF3;w.</p><p><strong>Podstawowe za&#x142;o&#x17C;enia TBD:</strong></p><ul><li>ci&#x105;g&#x142;a integracja z g&#x142;&#xF3;wnym branchem <em>trunk</em></li><li>u&#x17C;ywanie <em>short-term</em> feature branchy </li><li>bezpieczne techniki deploymentowe, cz&#x119;ste release&apos;owanie zmian na produkcj&#x119; z <em>trunk&apos;a</em></li><li>utrzymanie bardzo wysokiego pokrycia kodu aby zminimalizowa&#x107; ryzyko wdro&#x17C;enia bugow na produkcj&#x119;</li><li>u&#x17C;ycie feature flags jest niezb&#x119;dne (commitujemy zmiany dot. feature, kt&#xF3;re jeszcze jest inprogress)</li><li>silne nastawienie na automatyzacje, automatyczny deployment testy itd.</li><li>cz&#x119;ste merge do <em>trunk&apos;a </em>zmuszaj&#x105; do wytwarzania ma&#x142;ych zmian, kod powinien by&#x107; zawsze gotowy do wdro&#x17C;enia na produkcj&#x119;</li><li>cz&#x119;ste wdro&#x17C;enia</li></ul><p>W przeciwie&#x144;stwie do innych strategii gdzie branche mog&#x105; istnie&#x107; tygodnie lub nawet miesi&#x105;ce w TBD branche s&#x105; tymczasowe co oznacza, &#x17C;e programi&#x15B;ci tworz&#x105; branche typu feature lub fix i merguj&#x105; je zwykle do <em>trunk&apos;a </em>w ci&#x105;gu jednego dnia i usuwaj&#x105; tego brancha. Ta metoda minimalizuje ryzyko napotkania na konflikty a codebase jest bardziej uporz&#x105;dkowany i zorganizowany.</p><p><strong>CI &amp; Testy</strong></p><p>Continuous Integration jest bardzo istotne w TBD. Z uwagi na to, &#x17C;e programi&#x15B;ci bardzo cz&#x119;sto commituj&#x105; do <em>trunk&apos;a </em>(mastera) zachodzi potrzeba ci&#x105;g&#x142;ej integracji tj. budowania i testowania zmian automatycznie i og&#xF3;lnie sprawdzania jako&#x15B;ci kodu aby upewni&#x107; si&#x119;, &#x17C;e zmiana nie psuje mastera czy te&#x17C; nie ma konfliktu z innymi zmianami. Proces CI powinien automatycznie sprawdza&#x107; zmiany dokonane na masterze utrzymuj&#x105;c g&#x142;&#xF3;wnego brancha w dobrej kondycji, zawsze gotowego do wdro&#x17C;enia na produkcj&#x119;. Ka&#x17C;dy commit nale&#x17C;y interpretowa&#x107; jako potencjalnie kolejny release. Dzi&#x119;ki temu mamy szybsze cykle wdro&#x17C;eniowe i niski time-to-market.</p><h2 id="jak%C4%85-strategi%C4%99-wybra%C4%87">Jak&#x105; strategi&#x119; wybra&#x107;?</h2><p>Wyb&#xF3;w odpowiedniej strategii branchowania jest istotny, poniewa&#x17C; wp&#x142;ywa na spos&#xF3;b, w jaki zespo&#x142;y wsp&#xF3;&#x142;pracuj&#x105; nad projektem, zarz&#x105;dzaj&#x105; kodem, wdra&#x17C;aj&#x105; zmiany i zale&#x17C;y od charakterystyki projektu, wymaga&#x144; biznesowych i preferencji zespo&#x142;u. <strong>Git Flow </strong>jest bardziej skomplikowany ale zapewnia kontrol&#x119; nad procesem, nadaje si&#x119; do projekt&#xF3;w wymagaj&#x105;cych jasno okre&#x15B;lonych wyda&#x144;, szczeg&#xF3;&#x142;owego planowania i testowania. <strong>GitHub Flow </strong>jest prosty i dynamiczny, efektywny w projektach wymagaj&#x105;cych ci&#x105;g&#x142;ego wdra&#x17C;ania i szybkiego dostarczania nowych funkcji. <strong>TBD </strong>sprawdza si&#x119; w projektach wymagaj&#x105;cych szybkiego wdra&#x17C;ania, minimalizuje konflikty i utrzymuje prostot&#x119;, jednak nale&#x17C;y pami&#x119;ta&#x107;, &#x17C;e wymaga ca&#x142;ej otoczki automatyzacji, kt&#xF3;ra pozwoli bezpiecznie wdra&#x17C;a&#x107; zmiany na produkcj&#x119;.</p><h2 id="podsumowanie">Podsumowanie</h2><p>Mam nadziej&#x119;, &#x17C;e w klarowny i zrozumia&#x142;y spos&#xF3;b wyja&#x15B;ni&#x142;em Tobie czym charakteryzuj&#x105; si&#x119; ro&#x17C;ne strategie branchowania i zarz&#x105;dzania kodem. Je&#x17C;eli ten artyku&#x142; by&#x142; dla Ciebie warto&#x15B;ciowy podziel si&#x119; nim z innymi. Tymczasem zostawiam Ci&#x119; w klimacie progresywnego house mojego autorstwa, do us&#x142;yszenia!</p><figure class="kg-card kg-embed-card"><iframe width="200" height="113" src="https://www.youtube.com/embed/fOA9SPXoY2w?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen title="Deep Progressive House Mix | Melodic Journey"></iframe></figure>]]></content:encoded></item><item><title><![CDATA[Kubernetes - jak to zrozumieć i użyć w praktyce?]]></title><description><![CDATA[<p>Je&#x15B;li to czytasz, zapewne s&#x142;ysza&#x142;e&#x15B; ju&#x17C; termin Kubernetes. Zapewne czyta&#x142;e&#x15B; ju&#x17C; gdzie&#x15B;, &#x17C;e jest to narz&#x119;dzie do orkiestracji kontener&#xF3;w, &#x17C;e ma jakie&#x15B; powi&#x105;zania z Dockerem, &#x17C;e u&</p>]]></description><link>https://lsdev.pl/posts/kubernetes-krok-po-kroku/</link><guid isPermaLink="false">650dc4f5cc5ec47339e258a3</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[Docker]]></category><category><![CDATA[Software]]></category><category><![CDATA[DevOps]]></category><dc:creator><![CDATA[Dawid Warzocha]]></dc:creator><pubDate>Sun, 04 Feb 2024 22:51:34 GMT</pubDate><media:content url="https://lsdev.pl/content/images/2024/02/growtika-KU9ABpm7eV8-unsplash.webp" medium="image"/><content:encoded><![CDATA[<img src="https://lsdev.pl/content/images/2024/02/growtika-KU9ABpm7eV8-unsplash.webp" alt="Kubernetes - jak to zrozumie&#x107; i u&#x17C;y&#x107; w praktyce?"><p>Je&#x15B;li to czytasz, zapewne s&#x142;ysza&#x142;e&#x15B; ju&#x17C; termin Kubernetes. Zapewne czyta&#x142;e&#x15B; ju&#x17C; gdzie&#x15B;, &#x17C;e jest to narz&#x119;dzie do orkiestracji kontener&#xF3;w, &#x17C;e ma jakie&#x15B; powi&#x105;zania z Dockerem, &#x17C;e u&#x17C;ywa si&#x119; tego w chmurach, &#x17C;e ma super mechanizm automatycznego skalowania i &#x17C;e jest rozwi&#x105;zaniem na wszelkie z&#x142;o panuj&#x105;ce przy ci&#x105;gle rosn&#x105;cym &#x15B;rodowisku produkcyjnym. </p><p>Jednocze&#x15B;nie jednak masz &#x15B;wiadomo&#x15B;&#x107;, &#x17C;e nie jest to &#x142;atwa technologia, poniewa&#x17C; prawie wsz&#x119;dzie czytasz, &#x17C;e pr&#xF3;g wej&#x15B;cia w t&#x119; technologi&#x119; jest wysoki, a gdy pr&#xF3;bujesz ruszy&#x107; co&#x15B; z dokumentacj&#x105; Kubernetesa, ci&#x119;&#x17C;ko Ci si&#x119; zmierzy&#x107; z ogromem r&#xF3;&#x17C;nych nowych termin&#xF3;w. </p><p>Na dodatek gdy pr&#xF3;bujesz uruchomi&#x107; &#x15B;rodowisko u siebie na komputerze, to ci&#x105;gle co&#x15B; nie dzia&#x142;a, a oficjalna dokumentacja wydaje Ci si&#x119; niejasna i zbyt og&#xF3;lnikowa.</p><p>Zgad&#x142;em? Je&#x15B;li nie to bardzo przepraszam. Ale w&#x142;a&#x15B;ciwie dlaczego zak&#x142;adam, &#x17C;e jeste&#x15B; tak&#x105; osob&#x105;? Bo w rozmowach o Kubernetesie z innymi, w&#x142;a&#x15B;nie z takimi osobami najcz&#x119;&#x15B;ciej si&#x119; spotyka&#x142;em. W tym artykule chc&#x119; udowodni&#x107;, &#x17C;e Kubernetes nie jest tak trudny jak si&#x119; wydaje i mo&#x17C;na o nim opowiedzie&#x107; nieco... pro&#x15B;ciej.</p><p>&#x17B;eby jednak nie powtarza&#x107; wiedzy i ze wzgl&#x119;du na to, &#x17C;e Kubernetesa nie warto rusza&#x107; je&#x15B;li nie s&#x142;ysza&#x142;e&#x15B; nigdy wcze&#x15B;niej o Dockerze, przed dalsz&#x105; lektur&#x105; zalecam si&#x119; zapozna&#x107; z artyku&#x142;em o tym jak dzia&#x142;a sam Docker:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://lsdev.pl/posts/docker-w-praktyce/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Docker w praktyce. Dobre praktyki i przydatne polecenia</div><div class="kg-bookmark-description">Docker [https://www.docker.com/] jest przydatnym narz&#x119;dziem s&#x142;u&#x17C;&#x105;cym do
wirtualizacji na poziomie systemu operacyjnego. Oprogramowanie to pozwala na
uruchamianie proces&#xF3;w aplikacji w &#x201C;lekkich&#x201D; kontenerach odizolowanych od systemu
operacyjnego hosta. W pewnym sensie kontenery przypominaj&#x105; maszyny wir&#x2026;</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://lsdev.pl/assets/apple-touch-icon.png?v=f1baa8b3e2" alt="Kubernetes - jak to zrozumie&#x107; i u&#x17C;y&#x107; w praktyce?"><span class="kg-bookmark-author">lsdev.pl</span><span class="kg-bookmark-publisher">&#x141;ukasz</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://lsdev.pl/content/images/2022/05/docker.webp" alt="Kubernetes - jak to zrozumie&#x107; i u&#x17C;y&#x107; w praktyce?"></div></a></figure><p>Je&#x15B;li przeczyta&#x142;e&#x15B; ju&#x17C; artyku&#x142; powy&#x17C;ej i wiesz ju&#x17C; wszystko o Dockerze to w takim razie teraz...</p><h1 id="zapomnij-o-dockerze">Zapomnij o Dockerze</h1>
<p>Pewnie powiesz: &quot;Zaraz chwila... Ale jak to zapomnij? Pierw kaza&#x142;e&#x15B; przeczyta&#x107; artyku&#x142; o Dockerze i teraz mam o nim zapomnie&#x107;?&quot;.</p><p>W sumie tak, no mo&#x17C;e nie do ko&#x144;ca. Og&#xF3;lnie rzecz bior&#x105;c, kilka lat temu Kubernetes korzysta&#x142; z Dockera. Jednak od wersji 1.24 Kubernetes przesta&#x142; to robi&#x107; i oficjalnie ju&#x17C; nie wspiera Dockera (<a href="https://kubernetes.io/blog/2022/03/31/ready-for-dockershim-removal/?ref=lsdev.pl">Is Your Cluster Ready for v1.24? | Kubernetes</a>).</p><p>Czemu Kubernetes przesta&#x142; wspiera&#x107; Dockera? Bo nie mia&#x142;o to wi&#x119;kszego sensu. Docker wbrew temu co mo&#x17C;na przeczyta&#x107; na niekt&#xF3;rych portalach, samemu nie uruchamia kontener&#xF3;w. Do uruchomienia kontener&#xF3;w u&#x17C;ywa innego narz&#x119;dzia, kt&#xF3;re nazywa si&#x119; Containerd (<a href="https://containerd.io/?ref=lsdev.pl">containerd &#x2013; An industry-standard container runtime with an emphasis on simplicity, robustness and portability</a>).</p><p>Mo&#x17C;na powiedzie&#x107;, &#x17C;e Docker jest nak&#x142;adk&#x105; na Containerd, kt&#xF3;ra sprawia, &#x17C;e uruchamianie kontener&#xF3;w jest szybkie i proste.</p><p>Dlatego przed wersj&#x105; <strong>1.24</strong> Kubernetesa:</p><p><strong>Kubernetes </strong>przekazywa&#x142; polecenia do <strong>Dockera </strong>-&gt; <strong>Docker </strong>przekazywa&#x142; polecenia do <strong>Containerd </strong>-&gt; <strong>Containerd </strong>operowa&#x142; na kontenerach.</p><p>Od wersji <strong>1.24</strong> Kubernetesa:</p><p><strong>Kubernetes </strong>przekazuje polecenia do <strong>Containerd </strong>-&gt; <strong>Containerd  </strong>operuje na kontenerach.</p><p>Oczywi&#x15B;cie jest to bardzo uproszczony obraz, ale my&#x15B;l&#x119;, &#x17C;e wystarczaj&#x105;cy do tego aby zrozumie&#x107; wcze&#x15B;niejsze zale&#x17C;no&#x15B;ci z <em>Dockerem</em>.</p><h2 id="ale-po-co-mi-w%C5%82a%C5%9Bciwie-kubernetes">Ale po co mi w&#x142;a&#x15B;ciwie Kubernetes?</h2><p> Sam Docker zosta&#x142; stworzony g&#x142;&#xF3;wnie z my&#x15B;l&#x105; o programistach. Aplikacja umieszczona w kontenerze zadzia&#x142;a na ka&#x17C;dym komputerze i to jest jedna z mocnych stron tego rozwi&#x105;zania. Aplikacj&#x119; umieszczon&#x105; w Dockerze mo&#x17C;emy przekaza&#x107; innej osobie bez obaw o to, czy dana osoba ma na komputerze zainstalowane odpowiednie zale&#x17C;no&#x15B;ci, bo wiemy, &#x17C;e wszystko co potrzebne ju&#x17C; jest obecne w kontenerze. Mamy wi&#x119;c pewno&#x15B;&#x107;, &#x17C;e aplikacja zadzia&#x142;a zawsze, na ka&#x17C;dym systemie z zainstalowanym jedynie <em>Dockerem</em>.</p><p>Takie rozwi&#x105;zanie mo&#x17C;e wydawa&#x107; si&#x119; bardzo kusz&#x105;ce je&#x17C;eli chodzi o &#x15B;rodowiska produkcyjne. W ko&#x144;cu nie musimy si&#x119; martwi&#x107;, czy nasza aplikacja zadzia&#x142;a w tak cz&#x119;sto stresuj&#x105;cym momencie, kt&#xF3;rym jest wdra&#x17C;anie aplikacji na produkcj&#x119;. I co ciekawe <em>Docker</em> cz&#x119;sto jest r&#xF3;wnie&#x17C; w&#x142;a&#x15B;nie w taki spos&#xF3;b wykorzystywany.</p><p>Jednak pomimo tego, &#x17C;e <em>Docker</em> wiele spraw u&#x142;atwia, w <strong>wi&#x119;kszym </strong>&#x15B;rodowisku produkcyjnym mo&#x17C;e by&#x107; problematyczny w utrzymaniu. &#x17B;eby jednak lepiej to wyja&#x15B;ni&#x107;, pos&#x142;u&#x17C;&#x119; si&#x119; przyk&#x142;adem.</p><h2 id="kr%C3%B3tka-historia-pewnej-aplikacji">Kr&#xF3;tka Historia pewnej aplikacji</h2><p>Za&#x142;&#xF3;&#x17C;my, &#x17C;e masz napisan&#x105; aplikacj&#x119;, kt&#xF3;ra zosta&#x142;a umieszczona w kontenerze <em>Dockerowym</em>. Co&#x15B; bardzo prostego, jaka&#x15B; aplikacja napisana w PHP. Postanawiasz, &#x17C;e kupisz serwer, postawisz na tym jakiego&#x15B; <em>Linuxa</em>, zainstalujesz <em>Dockera</em> i opublikujesz stron&#x119; w internecie.</p><p>Strona okazuje si&#x119; sukcesem, klient&#xF3;w przybywa i Tw&#xF3;j serwer powoli niedomaga. Decydujesz si&#x119; na kupno kolejnego serwera, na kt&#xF3;rym te&#x17C; instalujesz <em>Dockera</em>. Na pierwszym serwerze instalujesz us&#x142;ug&#x119; load balancer typu <em>HAproxy</em>, aby roz&#x142;o&#x17C;y&#x107; ruch na obu serwerach.</p><p>Sukces aplikacji jednak jest tak du&#x17C;y, &#x17C;e z czasem i drugi serwer zaczyna Ci nie wystarcza&#x107;, dlatego dok&#x142;adasz kolejne serwery i kolejne... Na ka&#x17C;dym instalujesz <em>Dockera</em> i swoj&#x105; aplikacj&#x119;. Ka&#x17C;dy serwer dodajesz do backendu w <em>HAproxy</em>.</p><p>A&#x17C; w ko&#x144;cu kiedy dodajesz ju&#x17C; 20 serwer u&#x15B;wiadamiasz sobie, &#x17C;e trzeba zaktualizowa&#x107; aplikacj&#x119;. Dlatego logujesz si&#x119; na ka&#x17C;dy serwer i uruchamiasz now&#x105; wersj&#x119; aplikacji w <em>Dockerze</em>. &#x17B;eby klienci nie odczuli zmian, przed aktualizacj&#x105; ka&#x17C;dego z serwer&#xF3;w, po kolei zatrzymujesz na HAproxy ruch do obecnie aktualizowanego serwera.</p><p>Niestety, okazuje si&#x119;, &#x17C;e nowa wersja aplikacji ma b&#x142;&#x105;d i szybko musisz wr&#xF3;ci&#x107; do poprzedniej wersji. Dlatego znowu logujesz si&#x119; na ka&#x17C;dy serwer i przywracasz poprzedni&#x105; wersj&#x119;.  </p><p>&#x17B;eby temu zaradzi&#x107; na nast&#x119;pny raz automatyzujesz sobie wdra&#x17C;anie aplikacji pisz&#x105;c odpowiedni Playbook do Ansible.</p><p> Pewnego dnia z jakiego&#x15B; powodu pierwszy serwer, na kt&#xF3;rym jest <em>HAproxy</em>, ulega uszkodzeniu. Aplikacja przestaje dzia&#x142;a&#x107;. Po jakim&#x15B; czasie udaje Ci si&#x119; o&#x17C;ywi&#x107; serwer, ale klienci s&#x105; niepocieszeni.</p><p>&#x17B;eby si&#x119; przed tym uchroni&#x107; na nast&#x119;pny raz, postanawiasz zrobi&#x107; drugi serwer z HAproxy i tworzysz p&#x142;ywaj&#x105;cy pomi&#x119;dzy load balancerami adres IP, aby utrzyma&#x107; us&#x142;ug&#x119; ci&#x105;gle aktywn&#x105; nawet w przypadku awarii.</p><p>Po tym incydencie napotykasz kolejny problem, sza&#x142; na Twoj&#x105; aplikacj&#x119; min&#x105;&#x142;, jest ju&#x17C; mniej eksploatowana i serwery si&#x119; nudz&#x105;. &#x17B;eby zminimalizowa&#x107; koszty, rezygnujesz z kilku serwer&#xF3;w. Znowu wdra&#x17C;asz odpowiednie zmiany w konfiguracji tym razem ju&#x17C; na dw&#xF3;ch serwerach z HAproxy.</p><p> Po od&#x142;&#x105;czeniu serwer&#xF3;w okazuje si&#x119;, &#x17C;e jednak &#x17A;le obliczy&#x142;e&#x15B; obecne zu&#x17C;ycie i zrezygnowa&#x142;e&#x15B; ze zbyt du&#x17C;ej liczby serwer&#xF3;w, wi&#x119;c znowu dokupujesz kilka serwer&#xF3;w, znowu instalujesz na nich Dockera, wgrywasz aplikacj&#x119; i znowu dodajesz serwery do konfiguracji w <em>HAproxy</em>. </p><p>Po jakim&#x15B; czasie masz pomys&#x142; na inn&#x105; aplikacj&#x119;, kt&#xF3;r&#x105; postanawiasz wrzuci&#x107; na kilka serwer&#xF3;w jednak szybko okazuje si&#x119;, &#x17C;e serwery z zainstalowanymi dwoma aplikacjami jednocze&#x15B;nie niedomagaj&#x105;. Postanawiasz wi&#x119;c, &#x17C;e jedna cz&#x119;&#x15B;&#x107; serwer&#xF3;w b&#x119;dzie obs&#x142;ugiwa&#x107; jedn&#x105; aplikacj&#x119;, a druga cz&#x119;&#x15B;&#x107; drug&#x105; aplikacj&#x119;. </p><p>W g&#x142;owie masz ju&#x17C; jednak pomys&#x142; na trzeci&#x105;, niewielk&#x105; aplikacj&#x119; i &#x17C;eby sobie u&#x142;atwi&#x107; spraw&#x119;, zatrudniasz kolejnych administrator&#xF3;w, &#x17C;eby wdro&#x17C;enia robi&#x142;o kilka os&#xF3;b. </p><p>Niestety przy wdro&#x17C;eniu trzeciej aplikacji na istniej&#x105;ce ju&#x17C; serwery z poprzednimi aplikacjami, zaczynaj&#x105; szwankowa&#x107; dwie pierwsze, ze wzgl&#x119;du na to, &#x17C;e nowy administrator pope&#x142;ni&#x142; przypadkiem jaki&#x15B; b&#x142;&#x105;d. </p><p>&#x17B;eby temu zaradzi&#x107;, ograniczasz dost&#x119;p dla innych administrator&#xF3;w do cz&#x119;&#x15B;ci serwer&#xF3;w i nowe osoby wykonuj&#x105; wdro&#x17C;enia tylko na specjalnie wydzielonych do tego celu serwerach.</p><p>Podsumujmy. &#x17B;eby &#x15B;rodowisko oparte o Dockery bez orkiestracji dzia&#x142;a&#x142;o bez problem&#xF3;w przy du&#x17C;ej ilo&#x15B;ci serwer&#xF3;w, zarz&#x105;dzanych przez kilku administrator&#xF3;w, musisz samemu, za pomoc&#x105; r&#xF3;&#x17C;nych odpowiednich technologii zadba&#x107; o:</p><ol><li>odporno&#x15B;&#x107; na awari&#x119;</li><li>automatyzacj&#x119; wdro&#x17C;e&#x144; aplikacji ze strategi&#x105; zachowania 100% dost&#x119;pno&#x15B;ci</li><li>r&#xF3;wnowa&#x17C;enie ruchu i ewentualn&#x105; migracj&#x119; kontener&#xF3;w</li><li>monitoring ka&#x17C;dego serwera pod wzgl&#x119;dem zu&#x17C;ycia zasob&#xF3;w</li><li>ograniczanie dost&#x119;pu do serwer&#xF3;w</li></ol><p>I oczywi&#x15B;cie wszystkie powy&#x17C;sze problemy mo&#x17C;na rozwi&#x105;za&#x107; na kilka sposob&#xF3;w jednak...</p><h1 id="czy-mo%C5%BCna-pro%C5%9Bciej">Czy mo&#x17C;na pro&#x15B;ciej?</h1>
<p>Oczywi&#x15B;cie, i w&#x142;a&#x15B;nie po to jest Kubernetes. Klaster Kubernetes:</p><ol><li>Ma wbudowane mechanizmy, kt&#xF3;re odpowiadaj&#x105; za odporno&#x15B;&#x107; na awarie (sprz&#x119;tow&#x105; jak i r&#xF3;wnie&#x17C; awari&#x119; aplikacji w kontenerze)</li><li>Ma API, dzi&#x119;ki czemu wdro&#x17C;enie aplikacji jest wykonywane poprzez po&#x142;&#x105;czenie do jednego API, kt&#xF3;re &#x142;&#x105;czy wszystkie serwery po&#x142;&#x105;czone w jeden klaster Kubernetesa</li><li>Ma wbudowane mechnizmy do r&#xF3;wnowa&#x17C;enia obci&#x105;&#x17C;enia i automatycznej migracji kontener&#xF3;w</li><li>Ma wbudowany monitoring (w tym monitoring obci&#x105;&#x17C;enia) oraz mechanizmy logowania b&#x142;&#x119;d&#xF3;w</li><li>Ka&#x17C;de wdro&#x17C;enie mo&#x17C;e mie&#x107; swoje wydzielone miejsce (namespace), z ograniczonym dost&#x119;pem oraz zasobami, bez konieczno&#x15B;ci ograniczania go do konkretnych maszyn</li></ol><h2 id="w-porz%C4%85dku-ale-jak-to-dzia%C5%82a">W porz&#x105;dku, ale jak to dzia&#x142;a?</h2><p>Najcz&#x119;&#x15B;ciej w tym momencie wielu zaczyna t&#x142;umaczy&#x107; Ci czym s&#x105; terminy takie jak: pod, service, ingress, deployment, daemonset, replicaset, persistent volume, persistent volume claim, horizontal pod autoscaler i... inne.</p><p>Oczywi&#x15B;cie warto zna&#x107; te terminy, jednak uwa&#x17C;am, &#x17C;e zaczynanie nauki od studiowania definicji ka&#x17C;dego z powy&#x17C;szych element&#xF3;w, mo&#x17C;e zniech&#x119;ci&#x107; do dalszego zdobywania wiedzy. Tak naprawd&#x119;, aby zrozumie&#x107; jak dzia&#x142;a <em>Kubernetes</em>, najlepiej go po prostu u&#x17C;y&#x107; do czego&#x15B; prostego w praktyce.</p><p>Problem w tym, &#x17C;e je&#x15B;li chcemy u&#x17C;y&#x107; <em>Kubernetesa</em> w praktyce, korzystaj&#x105;c z narz&#x119;dzia <strong>kubectl </strong>i tak potrzebujemy zna&#x107; powy&#x17C;sze terminy. </p><figure class="kg-card kg-image-card"><img src="https://lsdev.pl/content/images/2024/02/meme-4.webp" class="kg-image" alt="Kubernetes - jak to zrozumie&#x107; i u&#x17C;y&#x107; w praktyce?" loading="lazy" width="1030" height="1280" srcset="https://lsdev.pl/content/images/size/w600/2024/02/meme-4.webp 600w, https://lsdev.pl/content/images/size/w1000/2024/02/meme-4.webp 1000w, https://lsdev.pl/content/images/2024/02/meme-4.webp 1030w" sizes="(min-width: 720px) 720px"></figure><p>Poza tym, tworzenie nowych wdro&#x17C;e&#x144; w <em>Kubernetesie</em>, zwykle wi&#x105;&#x17C;e si&#x119; z pisaniem tak zwanych manifest&#xF3;w, czyli zestawu polece&#x144;, kt&#xF3;re Kubernetes ma dla nas wykona&#x107; co dla niekt&#xF3;rych mo&#x17C;e wydawa&#x107; si&#x119; &#x15B;cian&#x105; nie do przeskoczenia.</p><p>Dlatego, &#x17C;eby by&#x142;o pro&#x15B;ciej, pos&#x142;u&#x17C;ymy si&#x119; czytelnym i &#x142;atwym w obs&#x142;udze <strong>Rancherem</strong>.</p><h2 id="czym-jest-rancher">Czym jest Rancher?</h2><p>Kr&#xF3;tko m&#xF3;wi&#x105;c jest to bardzo zaawansowany i popularny panel do zarz&#x105;dzania klastrami <em>Kubernetesa</em>. </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lsdev.pl/content/images/2023/09/image.png" class="kg-image" alt="Kubernetes - jak to zrozumie&#x107; i u&#x17C;y&#x107; w praktyce?" loading="lazy" width="1147" height="820" srcset="https://lsdev.pl/content/images/size/w600/2023/09/image.png 600w, https://lsdev.pl/content/images/size/w1000/2023/09/image.png 1000w, https://lsdev.pl/content/images/2023/09/image.png 1147w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Shell uruchomiony w Rancherze do po&#x142;&#x105;czenia z Podem zawieraj&#x105;cym kontener z Nginxem na Debianie</span></figcaption></figure><p>Z uwagi na bardzo prosty i przejrzysty interfejs, Rancher jest bardzo &#x142;atwy w u&#x17C;yciu. Poza tym jest darmowy i co najlepsze, Rancher potrafi napisa&#x107; manifesty za nas. U&#x17C;ytkownik musi jedynie wpisa&#x107; odpowiednie dane w bardzo prostym w u&#x17C;yciu formularzu.</p><p>Ciekawostk&#x105; jest to, &#x17C;e Rancher dzia&#x142;a w Kubernetesie. Postaram si&#x119; jednak u&#x142;atwi&#x107; proces instalacji lokalnego klastra Kubernetes oraz Ranchera do minimum.</p><h2 id="instalacja-k3s-i-ranchera">Instalacja K3s i Ranchera</h2><p>Rancher nie nale&#x17C;y do najl&#x17C;ejszych, dlatego komputer na kt&#xF3;rym go uruchomimy, najlepiej &#x17C;eby mia&#x142; przynajmniej 16 GB RAM. Osobi&#x15B;cie uruchomi&#x142;em go bez problemu na Surface Pro 8 na CPU i5 11 generacji od Intela i 16 GB RAM. Nie zalecam instalacji Ranchera w ten spos&#xF3;b przy mniejszej ilo&#x15B;ci dost&#x119;pnej pami&#x119;ci. </p><p>Jednak je&#x15B;li tw&#xF3;j komputer ma mniejsz&#x105; ilo&#x15B;&#x107; pami&#x119;ci, nadal mo&#x17C;esz nauczy&#x107; si&#x119; z tego poradnika jak zainstalowa&#x107; K3S w WSL v2 (i ewentualnie na w&#x142;asn&#x105; r&#x119;k&#x119; zainstalowa&#x107; co&#x15B; l&#x17C;ejszego, np. Kubernetes Dashboard). </p><p>Czym jednak jest <strong>K3S</strong>? Jest to lekka ale jednocze&#x15B;nie bardzo pot&#x119;&#x17C;na dystrybucja Kubernetesa. Jest bardzo prosta w instalacji i &#x15B;wietnie sprawdzi si&#x119; w ewentualnym wysoko dost&#x119;pnym &#x15B;rodowisku produkcyjnym.</p><p>Statystycznie rzecz bior&#x105;c, wi&#x119;kszo&#x15B;&#x107; os&#xF3;b korzysta z Windowsa, dlatego poka&#x17C;&#x119;, jak zainstalowa&#x107; Ranchera u&#x17C;ywaj&#x105;c:</p><p><strong>Windows -&gt; WSL (z systemem AlmaLinux 9) -&gt; K3S</strong></p><p>WSL (Windows Subsystem for Linux) pozwoli nam na szybk&#x105; instalacj&#x119; lekkiego Linuxa, na kt&#xF3;rym b&#x119;dziemy mogli eksperymentowa&#x107;.</p><p>Oczywi&#x15B;cie nic nie stoi na przeszkodzie, aby uruchomi&#x107; inn&#x105; dystrybucj&#x119; Linuxa na VirtualBoxie, Hyper-V, Proxmoxie czy Vmware, a nast&#x119;pnie na niej K3S. Jednak w tym poradniku u&#x17C;yjemy WSL v2 poniewa&#x17C; wydaje si&#x119; najwygodniejsz&#x105; i najszybsz&#x105; opcj&#x105;.</p><p>Instalacj&#x119; WSL dla Windows 10 i 11 mo&#x17C;na znale&#x17A;&#x107; tutaj (b&#x119;dzie to niezb&#x119;dne aby przej&#x15B;&#x107; dalej):</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://learn.microsoft.com/en-us/windows/wsl/install?ref=lsdev.pl"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Install WSL</div><div class="kg-bookmark-description">Install Windows Subsystem for Linux with the command, wsl --install. Use a Bash terminal on your Windows machine run by your preferred Linux distribution - Ubuntu, Debian, SUSE, Kali, Fedora, Pengwin, Alpine, and more are available.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://learn.microsoft.com/favicon.ico" alt="Kubernetes - jak to zrozumie&#x107; i u&#x17C;y&#x107; w praktyce?"><span class="kg-bookmark-author">Microsoft Learn</span><span class="kg-bookmark-publisher">craigloewen-msft</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://learn.microsoft.com/en-us/media/open-graph-image.png" alt="Kubernetes - jak to zrozumie&#x107; i u&#x17C;y&#x107; w praktyce?"></div></a></figure><p>Je&#x15B;li masz ju&#x17C; WSL, mo&#x17C;emy zainstalowa&#x107; tam Linuxa. Osobi&#x15B;cie proponuj&#x119; AlmaLinux 9, kt&#xF3;ry znajdziesz w Microsoft Store:</p><figure class="kg-card kg-image-card"><img src="https://lsdev.pl/content/images/2023/09/image-1.png" class="kg-image" alt="Kubernetes - jak to zrozumie&#x107; i u&#x17C;y&#x107; w praktyce?" loading="lazy" width="380" height="352"></figure><p>Po zainstalowaniu naszego nowego Linuxa pod WSL, uruchamiamy go i tworzymy nowego u&#x17C;ytkownika:</p><figure class="kg-card kg-image-card"><img src="https://lsdev.pl/content/images/2023/09/image-25.png" class="kg-image" alt="Kubernetes - jak to zrozumie&#x107; i u&#x17C;y&#x107; w praktyce?" loading="lazy" width="937" height="267" srcset="https://lsdev.pl/content/images/size/w600/2023/09/image-25.png 600w, https://lsdev.pl/content/images/2023/09/image-25.png 937w" sizes="(min-width: 720px) 720px"></figure><p>Domy&#x15B;lnie, Linux uruchomiony w WSL nie obs&#x142;uguje systemd, kt&#xF3;re jest potrzebne do instalacji <strong>K3S</strong>. Dlatego, &#x17C;eby uruchomi&#x107; systemd, musimy jako root edytowa&#x107; plik <strong>/etc/wsl.conf</strong> i doda&#x107; do niego nast&#x119;puj&#x105;ce linie:</p><pre><code class="language-bash">[boot]
systemd=true</code></pre><figure class="kg-card kg-image-card"><img src="https://lsdev.pl/content/images/2023/09/image-38.png" class="kg-image" alt="Kubernetes - jak to zrozumie&#x107; i u&#x17C;y&#x107; w praktyce?" loading="lazy" width="459" height="209"></figure><p>&#x17B;eby powy&#x17C;sza zmiana zosta&#x142;a zastosowana w Linuxie, musimy go zrestartowa&#x107;. W tym celu zamykamy kart&#x119; z naszym Linuxem i otwieramy kart&#x119; z PowerShellem w kt&#xF3;rym uruchamiamy polecenie <strong>wsl --shutdown</strong>:</p><figure class="kg-card kg-image-card"><img src="https://lsdev.pl/content/images/2023/09/image-39.png" class="kg-image" alt="Kubernetes - jak to zrozumie&#x107; i u&#x17C;y&#x107; w praktyce?" loading="lazy" width="326" height="93"></figure><p>To polecenie zamyka wszystkie systemy uruchomione w WSL.</p><p>Teraz mo&#x17C;emy ponownie uruchomi&#x107; kart&#x119; z AlmaLinuxem i sprawdzi&#x107; czy systemd dzia&#x142;a prawid&#x142;owo:</p><figure class="kg-card kg-image-card"><img src="https://lsdev.pl/content/images/2023/09/image-40.png" class="kg-image" alt="Kubernetes - jak to zrozumie&#x107; i u&#x17C;y&#x107; w praktyce?" loading="lazy" width="948" height="369" srcset="https://lsdev.pl/content/images/size/w600/2023/09/image-40.png 600w, https://lsdev.pl/content/images/2023/09/image-40.png 948w" sizes="(min-width: 720px) 720px"></figure><p>Kiedy mamy ju&#x17C; gotowy system do instalacji Kubernetesa, jako root uruchamiamy polecenie:</p><pre><code class="language-bash">curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION=v1.26.9+k3s1 sh -s - server --cluster-init</code></pre><figure class="kg-card kg-image-card"><img src="https://lsdev.pl/content/images/2023/09/image-41.png" class="kg-image" alt="Kubernetes - jak to zrozumie&#x107; i u&#x17C;y&#x107; w praktyce?" loading="lazy" width="1105" height="426" srcset="https://lsdev.pl/content/images/size/w600/2023/09/image-41.png 600w, https://lsdev.pl/content/images/size/w1000/2023/09/image-41.png 1000w, https://lsdev.pl/content/images/2023/09/image-41.png 1105w" sizes="(min-width: 720px) 720px"></figure><p>Jak widzimy, skrypt instalacyjny umie&#x15B;ci&#x142; kilka plik&#xF3;w w <strong>/usr/local/bin/</strong>. Dlatego dla u&#x142;atwienia dodajemy t&#x119; &#x15B;cie&#x17C;k&#x119; do PATH poleceniem:</p><pre><code class="language-bash">export PATH=$PATH:/usr/local/bin</code></pre><p>&#x17B;eby przy ka&#x17C;dym uruchomieniu terminala nie musie&#x107; za ka&#x17C;dym razem wykonywa&#x107; tego polecenia, warto doda&#x107; je na ko&#x144;cu pliku <strong>/root/.bashrc</strong>. </p><p>Teraz powinni&#x15B;my mie&#x107; mo&#x17C;liwo&#x15B;&#x107; u&#x17C;ywania polece&#x144; <strong>kubectl</strong>:</p><figure class="kg-card kg-image-card"><img src="https://lsdev.pl/content/images/2023/09/image-42.png" class="kg-image" alt="Kubernetes - jak to zrozumie&#x107; i u&#x17C;y&#x107; w praktyce?" loading="lazy" width="840" height="258" srcset="https://lsdev.pl/content/images/size/w600/2023/09/image-42.png 600w, https://lsdev.pl/content/images/2023/09/image-42.png 840w" sizes="(min-width: 720px) 720px"></figure><p>Nasz w pe&#x142;ni funkcjonalny Kubernetes jest ju&#x17C; gotowy. Powy&#x17C;ej widzimy list&#x119; <strong>Pod&#xF3;w </strong>w<strong> Namespace </strong>o nazwie<strong> kube-system</strong>, odpowiedzialnych za dzia&#x142;anie Kubernetesa.</p><p>Tutaj mo&#x17C;emy si&#x119; na chwil&#x119; zatrzyma&#x107; aby wyt&#x142;umaczy&#x107; kilka termin&#xF3;w.</p><p><strong>Pody </strong>to w zasadzie kontenery, w kt&#xF3;rych s&#x105; uruchomione aplikacje. Dlaczego wi&#x119;c nazywa si&#x119; to <strong>Pod</strong> zamiast po prostu kontener? Przede wszystkim dlatego, &#x17C;e jeden <strong>Pod </strong>mo&#x17C;e zawiera&#x107; w sobie wi&#x119;cej ni&#x17C; jeden kontener. Poza tym <strong>Pod</strong> opr&#xF3;cz tego, &#x17C;e zawiera w sobie kontenery, ma r&#xF3;wnie&#x17C; logik&#x119;, kt&#xF3;ra na przyk&#x142;ad sprawdza czy kontener w <strong>Podzie</strong> jest aktywny. <strong>Pod </strong>pomimo tego &#x17C;e mo&#x17C;e zawiera&#x107; kilka kontener&#xF3;w, zawsze ma jeden adres IP, kt&#xF3;re kontenery wewn&#x105;trz wsp&#xF3;&#x142;dziel&#x105;. Na powy&#x17C;szym zrzucie ekranu widzimy mi&#x119;dzy innymi uruchomione<strong> Pody</strong>:</p><ul><li><strong>coredns</strong> - wewn&#x119;trzna us&#x142;uga DNS s&#x142;u&#x17C;&#x105;ca do komunikacji pomi&#x119;dzy<strong> Podami </strong>w sieci Kubernetesa</li><li><strong>metrics-server</strong> - serwer metryk zbieraj&#x105;cy dane o zu&#x17C;yciu CPU i pami&#x119;ci przez kontenery. Potrzebny do dzia&#x142;ania polecenia <strong>kubectl top node</strong> czy autoskalowania.</li><li> <strong>traefik</strong> - kr&#xF3;tko m&#xF3;wi&#x105;c jest to proxy, kt&#xF3;re w nomenklaturze Kubernetesa nazywane jest <strong>Ingress Controllerem</strong>. &#x17B;eby zrozumie&#x107; jak dzia&#x142;a pos&#x142;u&#x17C;my si&#x119; przyk&#x142;adem bardzo popularnego serwera WWW, czyli Apache. W Apache tworzymy VirtualHosty (czyli jeszcze inaczej: podpi&#x119;te domeny w np. cPanel), &#x17C;eby przekierowa&#x107; u&#x17C;ytkownika po wykrytej nazwie domenowej na jaki&#x15B; plik lub inny serwer. W Kubernetesie tworzymy<strong> Ingressy </strong>aby przekierowa&#x107; u&#x17C;ytkownika po wykrytej nazwie domenowej na dan&#x105; us&#x142;ug&#x119; (<strong>Service</strong>), kt&#xF3;ra ko&#x144;cowo, przekieruje nas na jaki&#x15B; <strong>Pod</strong>.</li></ul><p>Z kolei <strong>Namespace </strong>to przestrze&#x144; w kt&#xF3;rej tworzone s&#x105; Pody. Mo&#x17C;emy tworzy&#x107; kilka <strong>Namespace&apos;&#xF3;w</strong>, &#x17C;eby uporz&#x105;dkowa&#x107; nasze Pody. Poza tym, je&#x15B;li chcemy na przyk&#x142;ad, komu&#x15B; udost&#x119;pni&#x107; klaster Kubernetes, mo&#x17C;emy ograniczy&#x107; dost&#x119;p do jednego <strong>Namespace, </strong>w kt&#xF3;rym kto&#x15B; b&#x119;dzie m&#xF3;g&#x142; wykona&#x107; deploy swojej aplikacji.</p><p>Wr&#xF3;&#x107;my jednak do instalacji panelu Rancher. Przed instalacj&#x105; potrzebujemy przygotowa&#x107; jak&#x105;&#x15B; domen&#x119;, kt&#xF3;r&#x105; przekierujemy na IP naszego Linuxa. &#x17B;eby jednak pozna&#x107; IP Linuxa uruchomionego w WSL, potrzebujemy zainstalowa&#x107; pakiet hostname:</p><figure class="kg-card kg-image-card"><img src="https://lsdev.pl/content/images/2023/09/image-43.png" class="kg-image" alt="Kubernetes - jak to zrozumie&#x107; i u&#x17C;y&#x107; w praktyce?" loading="lazy" width="682" height="753" srcset="https://lsdev.pl/content/images/size/w600/2023/09/image-43.png 600w, https://lsdev.pl/content/images/2023/09/image-43.png 682w"></figure><p>Jak wida&#x107; powy&#x17C;ej, po zainstalowaniu pakietu hostname i wykonaniu polecenia <strong>hostname -I</strong> mo&#x17C;emy zobaczy&#x107; adres IP systemu uruchomionego w WSL (adres pierwszy od lewej).</p><p>Teraz uruchamiamy w Windowsie uruchamiamy notatnik jako administrator i edytujemy plik <strong>/etc/hosts</strong></p><figure class="kg-card kg-image-card"><img src="https://lsdev.pl/content/images/2023/09/image-44.png" class="kg-image" alt="Kubernetes - jak to zrozumie&#x107; i u&#x17C;y&#x107; w praktyce?" loading="lazy" width="956" height="631" srcset="https://lsdev.pl/content/images/size/w600/2023/09/image-44.png 600w, https://lsdev.pl/content/images/2023/09/image-44.png 956w" sizes="(min-width: 720px) 720px"></figure><p>Po otwarciu tego pliku, dodajemy domen&#x119; np. <strong>rancher.local</strong>, kt&#xF3;r&#x105; przekierowujemy na adres IP Linuxa uruchomionego w WSL i zapisujemy plik</p><figure class="kg-card kg-image-card"><img src="https://lsdev.pl/content/images/2023/09/image-45.png" class="kg-image" alt="Kubernetes - jak to zrozumie&#x107; i u&#x17C;y&#x107; w praktyce?" loading="lazy" width="610" height="511" srcset="https://lsdev.pl/content/images/size/w600/2023/09/image-45.png 600w, https://lsdev.pl/content/images/2023/09/image-45.png 610w"></figure><p>Teraz prawie jeste&#x15B;my gotowi do instalacji Ranchera. Prawie poniewa&#x17C; b&#x119;dziemy potrzebowa&#x107; jeszcze narz&#x119;dzia <strong>Helm.</strong></p><p><strong>Helm </strong>jest mened&#x17C;erem pakiet&#xF3;w. Jest to taki troch&#x119; yum/dnf/apt/zypper tylko dla Kubernetesa.</p><p>&#x17B;eby zainstalowa&#x107; <strong>Helma</strong>, wystarczy wykona&#x107; polecenie:</p><pre><code class="language-bash">curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash</code></pre><figure class="kg-card kg-image-card"><img src="https://lsdev.pl/content/images/2023/09/image-46.png" class="kg-image" alt="Kubernetes - jak to zrozumie&#x107; i u&#x17C;y&#x107; w praktyce?" loading="lazy" width="959" height="176" srcset="https://lsdev.pl/content/images/size/w600/2023/09/image-46.png 600w, https://lsdev.pl/content/images/2023/09/image-46.png 959w" sizes="(min-width: 720px) 720px"></figure><p>Teraz mamy wszystko gotowe do tego, aby zainstalowa&#x107; Ranchera.</p><p>Dlatego teraz wykonujemy po kolei poni&#x17C;sze polecenia:</p><pre><code class="language-bash">export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
helm repo add rancher-latest https://releases.rancher.com/server-charts/latest

kubectl create namespace cattle-system

kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.11.0/cert-manager.crds.yaml

helm repo add jetstack https://charts.jetstack.io

helm repo update

helm install cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --version v1.11.0

helm install rancher rancher-latest/rancher \
  --namespace cattle-system \
  --set hostname=rancher.local \
  --set replicas=1 \
  --set bootstrapPassword=Haslo_do_panelu_rancher</code></pre><p>Po wykonaniu ostatniego polecenia, powinien przywita&#x107; nas nast&#x119;puj&#x105;cy komunikat:</p><figure class="kg-card kg-image-card"><img src="https://lsdev.pl/content/images/2023/09/image-47.png" class="kg-image" alt="Kubernetes - jak to zrozumie&#x107; i u&#x17C;y&#x107; w praktyce?" loading="lazy" width="1515" height="645" srcset="https://lsdev.pl/content/images/size/w600/2023/09/image-47.png 600w, https://lsdev.pl/content/images/size/w1000/2023/09/image-47.png 1000w, https://lsdev.pl/content/images/2023/09/image-47.png 1515w" sizes="(min-width: 720px) 720px"></figure><p>Teraz wykonuj&#x105;c polecenie:</p><pre><code class="language-bash">echo https://rancher.local/dashboard/?setup=$(kubectl get secret --namespace cattle-system bootstrap-secret -o go-template=&apos;{{.data.bootstrapPassword|base64decode}}&apos;)</code></pre><p>Otrzymamy adres, kt&#xF3;rym mo&#x17C;emy zalogowa&#x107; si&#x119; do naszego panelu Rancher. Po uruchomieniu tego adresu IP w przegl&#x105;darce, powinien ukaza&#x107; si&#x119; nam komunikat o nieprawid&#x142;owym certyfikacie (kt&#xF3;ry oczywi&#x15B;cie pomijamy), a nast&#x119;pnie panel Rancher:</p><figure class="kg-card kg-image-card"><img src="https://lsdev.pl/content/images/2023/09/image-48.png" class="kg-image" alt="Kubernetes - jak to zrozumie&#x107; i u&#x17C;y&#x107; w praktyce?" loading="lazy" width="970" height="598" srcset="https://lsdev.pl/content/images/size/w600/2023/09/image-48.png 600w, https://lsdev.pl/content/images/2023/09/image-48.png 970w" sizes="(min-width: 720px) 720px"></figure><p>Akceptujemy licencj&#x119; i przechodzimy dalej.</p><figure class="kg-card kg-image-card"><img src="https://lsdev.pl/content/images/2024/02/image.png" class="kg-image" alt="Kubernetes - jak to zrozumie&#x107; i u&#x17C;y&#x107; w praktyce?" loading="lazy" width="1414" height="689" srcset="https://lsdev.pl/content/images/size/w600/2024/02/image.png 600w, https://lsdev.pl/content/images/size/w1000/2024/02/image.png 1000w, https://lsdev.pl/content/images/2024/02/image.png 1414w" sizes="(min-width: 720px) 720px"></figure><p>Gotowe! Rancher jest gotowy do zarz&#x105;dzania naszym Kubernetesem.</p><p>Teraz klikamy na nazw&#x119; naszego klastra (local) i mo&#x17C;emy zaczyna&#x107; nasz&#x105; zabaw&#x119; z Kubernetesem.</p><figure class="kg-card kg-image-card"><img src="https://lsdev.pl/content/images/2024/02/image-1.png" class="kg-image" alt="Kubernetes - jak to zrozumie&#x107; i u&#x17C;y&#x107; w praktyce?" loading="lazy" width="1883" height="798" srcset="https://lsdev.pl/content/images/size/w600/2024/02/image-1.png 600w, https://lsdev.pl/content/images/size/w1000/2024/02/image-1.png 1000w, https://lsdev.pl/content/images/size/w1600/2024/02/image-1.png 1600w, https://lsdev.pl/content/images/2024/02/image-1.png 1883w" sizes="(min-width: 720px) 720px"></figure><h2 id="tworzymy-pierwszy-deployment"> Tworzymy pierwszy Deployment</h2><p>Kiedy mamy ju&#x17C; postawiony panel Rancher, mo&#x17C;emy w prosty spos&#xF3;b spr&#xF3;bowa&#x107; co&#x15B; umie&#x15B;ci&#x107; w klastrze Kubernetes. </p><p>Dla przyk&#x142;adu spr&#xF3;bujemy na naszym klastrze Kubernetes uruchomi&#x107;... <strong>WordPressa</strong>.</p><p>Jednak, &#x17C;eby uruchomi&#x107; WordPressa, potrzebujemy bazy danych MySQL. Oczywi&#x15B;cie mogliby&#x15B;my zainstalowa&#x107; MySQL na naszym Linuxie, ale po co skoro w prosty i szybki spos&#xF3;b, mo&#x17C;emy uruchomi&#x107; j&#x105; na naszym Kubernetesie.</p><p>W tym celu przechodzimy do zak&#x142;adki <strong>Workloads </strong>-&gt; <strong>Deployments</strong>, a nast&#x119;pnie klikamy<strong> Create</strong>.</p><p>W polu <strong>Name </strong>wpisujemy np. <strong>mariadb</strong>, a nast&#x119;pnie w polu<strong> Container image </strong>wpisujemy <strong>mariadb</strong>. W polu <strong>Container image</strong> istotne, &#x17C;eby&#x15B;my nie zrobili liter&#xF3;wki, poniewa&#x17C; tutaj decydujemy, jaki obraz ma zosta&#x107; pobrany.</p><p>Przy tym polu warto te&#x17C; doda&#x107;, &#x17C;e mo&#x17C;emy w nim wpisa&#x107; nazw&#x119; dowolnego obrazu dost&#x119;pnego w <a href="https://hub.docker.com/search?q=&amp;ref=lsdev.pl">Docker Hub</a></p><p>Teraz musimy sprawi&#x107;, aby nasz MySQL by&#x142; dost&#x119;pny w sieci klastra Kubernetes. W tym celu w sekcji <strong>Networking </strong>dodajemy<strong> Service </strong>typu<strong> Cluster IP</strong> o nazwie<strong> mariadb</strong>, i <strong>Private Container Port </strong>ustawiamy na<strong> 3306</strong>.</p><figure class="kg-card kg-image-card"><img src="https://lsdev.pl/content/images/2024/02/image-2.png" class="kg-image" alt="Kubernetes - jak to zrozumie&#x107; i u&#x17C;y&#x107; w praktyce?" loading="lazy" width="1535" height="820" srcset="https://lsdev.pl/content/images/size/w600/2024/02/image-2.png 600w, https://lsdev.pl/content/images/size/w1000/2024/02/image-2.png 1000w, https://lsdev.pl/content/images/2024/02/image-2.png 1535w" sizes="(min-width: 720px) 720px"></figure><p>Teraz schodzimy ni&#x17C;ej do sekcji <strong>Environment Variables</strong> i dodajemy nast&#x119;puj&#x105;ce zmienne typu <strong>Key/Value Pair</strong> (zgodnie z dokumentacj&#x105;<strong> </strong><a href="https://hub.docker.com/_/mariadb?ref=lsdev.pl">Docker Hub</a>):</p><pre><code class="language-bash">MARIADB_RANDOM_ROOT_PASSWORD = 1
MARIADB_USER = wordpress
MARIADB_PASSWORD = password
MARIADB_DATABASE = wordpress</code></pre><p>Ca&#x142;o&#x15B;&#x107; b&#x119;dzie wygl&#x105;da&#x107; nast&#x119;puj&#x105;co:</p><figure class="kg-card kg-image-card"><img src="https://lsdev.pl/content/images/2024/02/image-3.png" class="kg-image" alt="Kubernetes - jak to zrozumie&#x107; i u&#x17C;y&#x107; w praktyce?" loading="lazy" width="1319" height="381" srcset="https://lsdev.pl/content/images/size/w600/2024/02/image-3.png 600w, https://lsdev.pl/content/images/size/w1000/2024/02/image-3.png 1000w, https://lsdev.pl/content/images/2024/02/image-3.png 1319w" sizes="(min-width: 720px) 720px"></figure><p>Po wype&#x142;nieniu powy&#x17C;szych pul, klikamy <strong>Create</strong>.</p><figure class="kg-card kg-image-card"><img src="https://lsdev.pl/content/images/2024/02/image-4.png" class="kg-image" alt="Kubernetes - jak to zrozumie&#x107; i u&#x17C;y&#x107; w praktyce?" loading="lazy" width="1559" height="285" srcset="https://lsdev.pl/content/images/size/w600/2024/02/image-4.png 600w, https://lsdev.pl/content/images/size/w1000/2024/02/image-4.png 1000w, https://lsdev.pl/content/images/2024/02/image-4.png 1559w" sizes="(min-width: 720px) 720px"></figure><p>Je&#x15B;li wszystko wype&#x142;nili&#x15B;my prawid&#x142;owo, nasz kontener z MySQL powinien by&#x107; aktywny.</p><p>Je&#x15B;li chcemy dla zabawy wej&#x15B;&#x107; do utworzonego kontenera, mo&#x17C;emy klikn&#x105;&#x107; 3 kropki po prawej stronie i wybra&#x107; <strong>Execute Shell.</strong></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lsdev.pl/content/images/2024/02/image-5.png" class="kg-image" alt="Kubernetes - jak to zrozumie&#x107; i u&#x17C;y&#x107; w praktyce?" loading="lazy" width="1474" height="613" srcset="https://lsdev.pl/content/images/size/w600/2024/02/image-5.png 600w, https://lsdev.pl/content/images/size/w1000/2024/02/image-5.png 1000w, https://lsdev.pl/content/images/2024/02/image-5.png 1474w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Domy&#x15B;lny kontener z Mariadb jest uruchomiony na systemie Ubuntu&#xA0;</span></figcaption></figure><p>Skoro nasza baza danych jest ju&#x17C; gotowa, mo&#x17C;emy teraz uruchomi&#x107; naszego WordPressa. &#x17B;eby to zrobi&#x107;, tworzymy kolejny Deployment.</p><p>Tym razem w polu <strong>Name </strong>wpisujemy np. <strong>wordpress</strong>, nast&#x119;pnie w polu<strong> Container image </strong>wpisujemy <strong>wordpress</strong>.</p><p>Nast&#x119;pnie w sekcji <strong>Networking </strong>dodajemy<strong> Service </strong>typu<strong> Node Port</strong> o nazwie<strong> wordpress. Private Container Port </strong>ustawiamy na 80, z kolei<strong> Listening Port </strong>ustawiamy na<strong> 30001</strong>.</p><figure class="kg-card kg-image-card"><img src="https://lsdev.pl/content/images/2024/02/image-6.png" class="kg-image" alt="Kubernetes - jak to zrozumie&#x107; i u&#x17C;y&#x107; w praktyce?" loading="lazy" width="1538" height="832" srcset="https://lsdev.pl/content/images/size/w600/2024/02/image-6.png 600w, https://lsdev.pl/content/images/size/w1000/2024/02/image-6.png 1000w, https://lsdev.pl/content/images/2024/02/image-6.png 1538w" sizes="(min-width: 720px) 720px"></figure><p>Teraz schodzimy ni&#x17C;ej do sekcji <strong>Environment Variables</strong> i dodajemy nast&#x119;puj&#x105;ce zmienne typu <strong>Key/Value Pair</strong>:</p><pre><code class="language-bash">WORDPRESS_DB_HOST = mariadb
WORDPRESS_DB_USER = wordpress
WORDPRESS_DB_PASSWORD = password
WORDPRESS_DB_NAME = wordpress</code></pre><p>Tutaj warto si&#x119; zatrzyma&#x107; przy zmiennej <strong>WORDPRESS_DB_HOST</strong>. Tutaj w standardowej instalacji np. na hostingu wsp&#xF3;&#x142;dzielonym, podaliby&#x15B;my IP lub nazw&#x119; domenow&#x105; serwera MySQL.</p><p>Jednak w Kubernetesie, musimy poda&#x107; nazw&#x119; stworzonego <strong>Service</strong>, kieruj&#x105;cego na <strong>Pod</strong> z serwerem<strong> MySQL</strong>.<strong> </strong>W naszym przypadku<strong> Service </strong>nazywa si&#x119; po prostu <strong>mariadb</strong>. Jednak je&#x15B;li nie wiedzieliby&#x15B;my jak&#x105; nazw&#x119; ma nasz<strong> Service</strong>, mo&#x17C;na to sprawdzi&#x107;<strong> </strong>w zak&#x142;adce<strong> Service Discovery </strong>-&gt;<strong> Services</strong></p><figure class="kg-card kg-image-card"><img src="https://lsdev.pl/content/images/2024/02/image-12.png" class="kg-image" alt="Kubernetes - jak to zrozumie&#x107; i u&#x17C;y&#x107; w praktyce?" loading="lazy" width="1343" height="412" srcset="https://lsdev.pl/content/images/size/w600/2024/02/image-12.png 600w, https://lsdev.pl/content/images/size/w1000/2024/02/image-12.png 1000w, https://lsdev.pl/content/images/2024/02/image-12.png 1343w" sizes="(min-width: 720px) 720px"></figure><p>Ca&#x142;o&#x15B;&#x107; b&#x119;dzie wi&#x119;c wygl&#x105;da&#x107; nast&#x119;puj&#x105;co:</p><figure class="kg-card kg-image-card"><img src="https://lsdev.pl/content/images/2024/02/image-7.png" class="kg-image" alt="Kubernetes - jak to zrozumie&#x107; i u&#x17C;y&#x107; w praktyce?" loading="lazy" width="1292" height="372" srcset="https://lsdev.pl/content/images/size/w600/2024/02/image-7.png 600w, https://lsdev.pl/content/images/size/w1000/2024/02/image-7.png 1000w, https://lsdev.pl/content/images/2024/02/image-7.png 1292w" sizes="(min-width: 720px) 720px"></figure><p>Klikamy <strong>Create </strong>i po kilku chwilach powinni&#x15B;my zobaczy&#x107; taki oto widok:</p><figure class="kg-card kg-image-card"><img src="https://lsdev.pl/content/images/2024/02/image-8.png" class="kg-image" alt="Kubernetes - jak to zrozumie&#x107; i u&#x17C;y&#x107; w praktyce?" loading="lazy" width="1573" height="334" srcset="https://lsdev.pl/content/images/size/w600/2024/02/image-8.png 600w, https://lsdev.pl/content/images/size/w1000/2024/02/image-8.png 1000w, https://lsdev.pl/content/images/2024/02/image-8.png 1573w" sizes="(min-width: 720px) 720px"></figure><p>W porz&#x105;dku, kontenery dzia&#x142;aj&#x105;. Ale co z naszym WordPressem?</p><p>Ot&#xF3;&#x17C; wystawili&#x15B;my go na porcie 30001. Wchodzimy wi&#x119;c pod adres:</p><p>http://rancher.local:30001</p><p>Je&#x15B;li wszystko zrobili&#x15B;my prawid&#x142;owo, powinni&#x15B;my ujrze&#x107; instalator Wordpressa:</p><figure class="kg-card kg-image-card"><img src="https://lsdev.pl/content/images/2024/02/image-9.png" class="kg-image" alt="Kubernetes - jak to zrozumie&#x107; i u&#x17C;y&#x107; w praktyce?" loading="lazy" width="1006" height="745" srcset="https://lsdev.pl/content/images/size/w600/2024/02/image-9.png 600w, https://lsdev.pl/content/images/size/w1000/2024/02/image-9.png 1000w, https://lsdev.pl/content/images/2024/02/image-9.png 1006w" sizes="(min-width: 720px) 720px"></figure><p>Prawda, &#x17C;e to by&#x142;o proste?</p><p>No tak, ale co my w&#x142;a&#x15B;ciwie zrobili&#x15B;my? </p><ol><li>Utworzyli&#x15B;my <strong>Deployment</strong>, kt&#xF3;ry utworzy&#x142; nam <strong>Pod </strong>z kontenerem zwieraj&#x105;cym MySQL, oraz <strong>Service </strong>nas&#x142;uchuj&#x105;cy na porcie 3306 w sieci <strong>klastra Kubernetes</strong> (<strong>Cluster IP</strong>) i kieruj&#x105;cy na ten Pod.</li><li>Utworzyli&#x15B;my drugi <strong>Deployment</strong>, kt&#xF3;ry utworzy&#x142; nam <strong>Pod </strong>z kontenerem zawieraj&#x105;cym <strong>WordPressa</strong>, oraz <strong>Service </strong>typu <strong>Node Port </strong>nas&#x142;uchuj&#x105;cy na zewn&#x105;trz na porcie 30001 i kieruj&#x105;cy na ten <strong>Pod</strong>.</li></ol><p>Gdybym napisa&#x142;bym to na pocz&#x105;tku, mo&#x17C;na by by&#x142;o uzna&#x107; powy&#x17C;sze s&#x142;owa za jak&#x105;&#x15B; czarn&#x105; magi&#x119;. Jednak u&#x17C;ycie odpowiedniego narz&#x119;dzia potrafi zmieni&#x107; perspektyw&#x119;. </p><p>Kiedy mamy ju&#x17C; gotowy Deployment z WordPressem, zobaczmy jeszcze jak w bardzo prosty spos&#xF3;b, mo&#x17C;emy skalowa&#x107; nasz&#x105; aplikacj&#x119;.</p><p>&#x17B;eby to zrobi&#x107; wystarczy, &#x17C;e w zak&#x142;adce <strong>Health </strong>klikniemy strza&#x142;k&#x119; w d&#xF3;&#x142;, a nast&#x119;pnie klikniemy znak +</p><figure class="kg-card kg-image-card"><img src="https://lsdev.pl/content/images/2024/02/image-10.png" class="kg-image" alt="Kubernetes - jak to zrozumie&#x107; i u&#x17C;y&#x107; w praktyce?" loading="lazy" width="348" height="285"></figure><p>Po kilku chwilach powinien si&#x119; pojawi&#x107; kolejny kontener z naszym WordPressem.</p><p>Mo&#x17C;emy to zobaczy&#x107; w zak&#x142;adce <strong>Workloads </strong>-&gt; <strong>Pods</strong></p><figure class="kg-card kg-image-card"><img src="https://lsdev.pl/content/images/2024/02/image-11.png" class="kg-image" alt="Kubernetes - jak to zrozumie&#x107; i u&#x17C;y&#x107; w praktyce?" loading="lazy" width="1823" height="411" srcset="https://lsdev.pl/content/images/size/w600/2024/02/image-11.png 600w, https://lsdev.pl/content/images/size/w1000/2024/02/image-11.png 1000w, https://lsdev.pl/content/images/size/w1600/2024/02/image-11.png 1600w, https://lsdev.pl/content/images/2024/02/image-11.png 1823w" sizes="(min-width: 720px) 720px"></figure><p>Teraz ruch na stronie http://rancher.local:30001 b&#x119;dzie roz&#x142;o&#x17C;ony jednocze&#x15B;nie na dw&#xF3;ch kontenerach. Mo&#x17C;esz oczywi&#x15B;cie utworzy&#x107; wi&#x119;cej Pod&#xF3;w, je&#x15B;li Tw&#xF3;j komputer to ud&#x17A;wignie.</p><p>Przy skalowaniu ciekaw&#x105; rzecz&#x105;, kt&#xF3;ra mo&#x17C;e wydawa&#x107; si&#x119; nieintuicyjna jest to, &#x17C;e gdy spr&#xF3;bujesz usun&#x105;&#x107; dany Pod, zaraz po jego usuni&#x119;ciu na jego miejsce pojawi si&#x119; drugi. Odpowiada za to tak zwany <strong>ReplicaSet </strong>jednak to ju&#x17C; jest materia&#x142; na inny artyku&#x142; &#x1F60A;</p><h2 id="podsumowanie">Podsumowanie</h2><p>Jak widzisz, u&#x17C;ywaj&#x105;c odpowiednich narz&#x119;dzi, utworzenie czego&#x15B; w klastrze i zarz&#x105;dzanie klastrem Kubernetes nie musi by&#x107; trudne. </p><p>Oczywi&#x15B;cie to co opisa&#x142;em wy&#x17C;ej to zaledwie kropla tego, co mo&#x17C;emy zrobi&#x107; w Kubernetesie. Na Kubernetesie z&#x142;o&#x17C;onym z jednej maszyny, nie mo&#x17C;emy w praktyce do&#x15B;wiadczy&#x107; pot&#x119;gi tego rozwi&#x105;zania. Potraktuj jednak ten artyku&#x142; jako taki ca&#x142;kowicie podstawowy wst&#x119;p do nauki tej technologii. </p><p>Narz&#x119;dzia takie jak K3S i Rancher skutecznie pozwalaj&#x105; pokona&#x107; strom&#x105; krzyw&#x105; uczenia si&#x119; Kubernetesa, a jednocze&#x15B;nie cz&#x119;sto s&#x105; u&#x17C;ywane w &#x15B;rodowiskach produkcyjnych, dlatego na pewno warto je zna&#x107;.</p><p>Na koniec zach&#x119;cam Ci&#x119; do eksperymentowania z Kubernetesem w panelu Rancher i pr&#xF3;bowania r&#xF3;&#x17C;nych konfiguracji i obraz&#xF3;w kontener&#xF3;w. </p><p>Mam nadziej&#x119;, &#x17C;e chocia&#x17C; troch&#x119; pomog&#x142;em Ci si&#x119; oswoi&#x107; z t&#x105; technologi&#x105; oraz oczywi&#x15B;cie &#x17C;ycz&#x119; powodzenia w nauce Kubernetesa &#x1F60A; </p>]]></content:encoded></item><item><title><![CDATA[Dokumentowanie oprogramowania]]></title><description><![CDATA[<p><strong>Dokumentacja </strong>jest kluczowa dla skutecznego zarz&#x105;dzania, utrzymania i rozwijania systemu. Dobra dokumentacja u&#x142;atwia zrozumienie funkcji, interfejs&#xF3;w i og&#xF3;lnej architektury systemu co u&#x142;atwia wsp&#xF3;&#x142;prac&#x119; mi&#x119;dzy zespo&#x142;ami a tak&#x17C;e usprawnia onboarding nowych</p>]]></description><link>https://lsdev.pl/posts/dokumentowanie-oprogramowania/</link><guid isPermaLink="false">65ba19212b45b18f866eb155</guid><category><![CDATA[Software]]></category><category><![CDATA[Architecture]]></category><category><![CDATA[Programming]]></category><dc:creator><![CDATA[Łukasz]]></dc:creator><pubDate>Wed, 31 Jan 2024 13:15:47 GMT</pubDate><media:content url="https://lsdev.pl/content/images/2024/01/nejc-soklic-wO42Rmamef8-unsplash--1-.webp" medium="image"/><content:encoded><![CDATA[<img src="https://lsdev.pl/content/images/2024/01/nejc-soklic-wO42Rmamef8-unsplash--1-.webp" alt="Dokumentowanie oprogramowania"><p><strong>Dokumentacja </strong>jest kluczowa dla skutecznego zarz&#x105;dzania, utrzymania i rozwijania systemu. Dobra dokumentacja u&#x142;atwia zrozumienie funkcji, interfejs&#xF3;w i og&#xF3;lnej architektury systemu co u&#x142;atwia wsp&#xF3;&#x142;prac&#x119; mi&#x119;dzy zespo&#x142;ami a tak&#x17C;e usprawnia onboarding nowych pracownik&#xF3;w. Dobra dokumentacja oznacza oszcz&#x119;dno&#x15B;&#x107; czasu (i pieni&#x119;dzy), kt&#xF3;ry w innym przypadku musia&#x142;by by&#x107; przeznaczony na spotkania, ustalenia itd.</p><h3 id="cechy-dobrej-dokumentacji">Cechy dobrej dokumentacji</h3><ul><li>zrozumia&#x142;a - napisana w przyst&#x119;pny spos&#xF3;b bez zawi&#x142;ych i skomplikowanych termin&#xF3;w technicznych.</li><li>aktualna - regularnie aktualizowana.</li><li>kompletna - wszystkie istotne funkcje i elementy systemu powinny by&#x107; dok&#x142;adnie opisane w dokumentacji, powinna zawiera&#x107; informacje na temat instalacji, konfiguracji, u&#x17C;ywania, zwalczania problem&#xF3;w oraz utrzymania systemu.</li><li>&#x142;atwo dost&#x119;pna i dobrze zorganizowana - u&#x17C;ytkownicy powinni mie&#x107; mo&#x17C;liwo&#x15B;&#x107; &#x142;atwego znalezienia potrzebnych informacji.</li></ul><h3 id="co-warto-umie%C5%9Bci%C4%87-w-dokumentacji">Co warto umie&#x15B;ci&#x107; w dokumentacji</h3><p>Dokumentacja powinna zawiera&#x107; przede wszystkim wiedz&#x119; biznesow&#x105; systemu. Zwykle t&#x105; cz&#x119;&#x15B;&#x107; dokumentacji utrzymuj&#x105; analitycy biznesowi i product ownerzy, gdy&#x17C; to oni wiedz&#x105; o zmianach jako pierwsi. Techniczne aspekty systemu r&#xF3;wnie&#x17C; powinny znale&#x17A;&#x107; si&#x119; w dokumentacji. Oczywi&#x15B;cie mo&#x17C;na by&#x107; zwolennikiem samo dokumentuj&#x105;cego si&#x119; kodu ale cz&#x119;sto mo&#x17C;e by&#x107; to za ma&#x142;o. Dlatego warto aby zesp&#xF3;&#x142;, g&#x142;&#xF3;wnie developerzy postarali si&#x119; o aktualizowanie dokumentacji w tym kontek&#x15B;cie np. w postaci <a href="https://bulldogjob.pl/readme/czym-jest-architecture-decision-record?ref=lsdev.pl">ADR</a> (<em>Architecture Decision Record</em>).</p><p>Przyk&#x142;adowe elementy, kt&#xF3;re warto umie&#x15B;ci&#x107; w dokumentacji:</p><ul><li><strong>Funkcje i odpowiedzialno&#x15B;ci (zarys biznesowy)&#x200C;&#x200C;</strong><br>Za co mikroserwis jest odpowiedzialny, jakie dane przetwarza i jakie pe&#x142;ni funkcje w kontek&#x15B;cie ca&#x142;ego systemu, kto jest opiekunem.</li><li><strong>Stack technologiczny&#x200C;&#x200C;</strong><br>Opis systemu w kontek&#x15B;cie u&#x17C;ytych technologii, aktualizowany na bie&#x17C;&#x105;co wraz z wyja&#x15B;nieniem dlaczego wybrali&#x15B;my takie technologie a nie inne. Warto pomy&#x15B;le&#x107; o utrzymywaniu <a href="https://bulldogjob.pl/readme/czym-jest-architecture-decision-record?ref=lsdev.pl">ADR</a> (<em>Architecture Decision Record</em>), kt&#xF3;ry m&#xF3;g&#x142;by by&#x107; nawet przechowywany w repozytorium kodu.</li><li><strong>&#x15A;rodowiska&#x200C;&#x200C;</strong><br>Spis &#x15B;rodowisk (np. INT, PREP, PROD itd).</li><li><strong>Repozytorium&#x200C;&#x200C;</strong><br>Spis wszystkich repozytori&#xF3;w git rozwijanych w ramach danego podsystemu.</li><li><strong>Dokumentacja API&#x200C;&#x200C;</strong><br>Zbi&#xF3;r informacji opisuj&#x105;cych funkcje, zasady i sposoby korzystania z danego interfejsu programistycznego. Jej istotn&#x105; rol&#x105; jest umo&#x17C;liwienie developerom zrozumienia jak prawid&#x142;owo korzysta&#x107; z API, jakie s&#x105; dost&#x119;pne funkcje, jakie parametry przyjmuj&#x105; &#x17C;&#x105;dania oraz jakich odpowiedzi mo&#x17C;emy si&#x119; spodziewa&#x107;.<br>Powinna by&#x107; mo&#x17C;liwo&#x15B;&#x107; pobrania aktualnej specyfikacji API w formacie <em>yaml/json </em>zgodnej z standardem openapi (wsdl w przypadku SOAP). Pomocnym narz&#x119;dziem mo&#x17C;e si&#x119; okaza&#x107; <a href="https://swagger.io/?ref=lsdev.pl">swagger</a>.</li><li><strong>Interakcje z innymi systemami</strong><br>Opis integracji z innymi systemami w tym wymagane protoko&#x142;y komunikacyjne.</li><li><strong>Instrukcje instalacji i konfiguracji</strong><br>Instrukcja zawieraj&#x105;ca wa&#x17C;ne informacje o instalacji czy te&#x17C; uruchomieniu aplikacji w szczeg&#xF3;lno&#x15B;ci uruchomieniu na lokalnym &#x15B;rodowisku developerskim co pozwoli szybko wdro&#x17C;y&#x107; si&#x119; w system nowemu developerowi. Idealnie aby ka&#x17C;de repo posiada&#x142;o <em>README.md</em>, w kt&#xF3;rym b&#x119;dzie opisane jak uruchomi&#x107; system lokalnie.</li><li><strong>Instrukcje u&#x17C;ytkowania</strong><br>Opis jak korzysta&#x107; z r&#xF3;&#x17C;nych funkcji i narz&#x119;dzi systemu.</li><li><strong>Proces CI/CD</strong><br>Chcemy tutaj opisa&#x107; proces jaki zachodzi od zmiany kodu przez developera w ramach jakiej&#x15B; funkcjonalno&#x15B;ci po wdro&#x17C;enie zmian na produkcj&#x119;.</li><li><strong>Changelog</strong><br>Chcemy dokumentowa&#x107; wszystkie zmiany w pliku changelog najlepiej przechowywanym w repozytorium kodu. <a href="https://gist.github.com/juampynr/4c18214a8eb554084e21d6e288a18a2c?ref=lsdev.pl">Przyk&#x142;ad</a> pliku changelog.</li><li><strong>Testowanie</strong><br>W jaki spos&#xF3;b podchodzimy do testowania mikroserwisu, jakie testy wykonujemy.</li><li><strong>Konwencje</strong><br>Informacje dla developer&#xF3;w, je&#x17C;eli chodzi o konwencje kodowania (np. pliki checkstyle) lub inne wa&#x17C;ne informacje.</li><li><strong>Awarie</strong><br>Rejestr awarii zawieraj&#x105;cy opis co si&#x119; wydarzy&#x142;o, kiedy, co by&#x142;o przyczyn&#x105; i jak uda&#x142;o si&#x119; naprawi&#x107; oraz inne przydatne informacje.</li><li><strong>Inne</strong><br>Inne przydatne informacje np. informacje kontaktowe dla wsparcia technicznego.</li></ul><p>Oczywi&#x15B;cie jest to tylko og&#xF3;lna propozycja tego co mog&#x142;oby si&#x119; znale&#x17A;&#x107; w dokumentacji. W zale&#x17C;no&#x15B;ci od specyfiki projektu mo&#x17C;e wygl&#x105;da&#x107; to inaczej.</p><h3 id="podsumowanie">Podsumowanie</h3><p>Inwestycja w szczeg&#xF3;&#x142;ow&#x105; i zrozumia&#x142;&#x105; dokumentacj&#x119; oprogramowania to inwestycja w przysz&#x142;o&#x15B;&#x107; - oszcz&#x119;dza czas, zasoby i zwi&#x119;ksza efektywno&#x15B;&#x107; pracy zespo&#x142;u. Je&#x17C;eli masz jakie&#x15B; ciekawe spostrze&#x17C;enia na ten temat komentarz b&#x119;dzie mile widziany &#x1F64C;.</p>]]></content:encoded></item><item><title><![CDATA[Planowanie zadań (Java & Spring)]]></title><description><![CDATA[<p>Projektuj&#x105;c z&#x142;o&#x17C;one aplikacje cz&#x119;sto zachodzi potrzeba wykonania jakiej&#x15B; funkcji w przysz&#x142;o&#x15B;ci - jednorazowo lub cyklicznie. Wystarczy wzi&#x105;&#x107; na warsztat aplikacj&#x119;, kt&#xF3;ra przypomina nam o r&#xF3;&#x17C;nych wydarzeniach, wysy&#x142;a</p>]]></description><link>https://lsdev.pl/posts/planowanie-zadan-w-java-i-spring/</link><guid isPermaLink="false">6439d856a8dd77c6f9f21623</guid><category><![CDATA[Java]]></category><category><![CDATA[Spring]]></category><category><![CDATA[Programming]]></category><dc:creator><![CDATA[Łukasz]]></dc:creator><pubDate>Sat, 15 Apr 2023 01:22:46 GMT</pubDate><media:content url="https://lsdev.pl/content/images/2023/04/michael-dziedzic-gEN5Btvf2Eg-unsplash.webp" medium="image"/><content:encoded><![CDATA[<img src="https://lsdev.pl/content/images/2023/04/michael-dziedzic-gEN5Btvf2Eg-unsplash.webp" alt="Planowanie zada&#x144; (Java &amp; Spring)"><p>Projektuj&#x105;c z&#x142;o&#x17C;one aplikacje cz&#x119;sto zachodzi potrzeba wykonania jakiej&#x15B; funkcji w przysz&#x142;o&#x15B;ci - jednorazowo lub cyklicznie. Wystarczy wzi&#x105;&#x107; na warsztat aplikacj&#x119;, kt&#xF3;ra przypomina nam o r&#xF3;&#x17C;nych wydarzeniach, wysy&#x142;a cyklicznie maile lub powiadomienia, czy te&#x17C; dokonuje p&#x142;atno&#x15B;ci w modelu subskrypcyjnym.</p><p>Wymienione wy&#x17C;ej funkcjonalno&#x15B;ci wymagaj&#x105; implementacji jakiego&#x15B; mechanizmu, kt&#xF3;ry pozwoli nam to zrealizowa&#x107; w tle czyli asynchronicznie. W tym artykule postaram si&#x119; to om&#xF3;wi&#x107; na przyk&#x142;adzie ekosystemu Java.</p><h2 id="dlaczego-asynchronicznie">Dlaczego asynchronicznie?</h2><p>Zadania w tle s&#x105; przydatnym mechanizmem i mo&#x17C;liwo&#x15B;ci&#x105; dla systemu sprawdzenia swojego wewn&#x119;trznego stanu i na jego podstawie w razie potrzeby wykonania pewnych akcji np. wys&#x142;anie maila, przygotowanie raportu czy te&#x17C; innego rodzaju usp&#xF3;jnienie systemu.</p><p>Wyobra&#x17A;my sobie, &#x17C;e chcemy aby nasz system wygenerowa&#x142; jaki&#x15B; skomplikowany raport analityczny. Wygenerowanie wspomnianego raportu mo&#x17C;e by&#x107; do&#x15B;&#x107; czasoch&#x142;onne gdy&#x17C; system musi najpierw pobra&#x107; dane z jakich&#x15B; zewn&#x119;trznych &#x17A;r&#xF3;de&#x142; i je przetworzy&#x107; w sensowny spos&#xF3;b w odpowiednim formacie.</p><p>Bez wzgl&#x119;du na to czy jest to aplikacja desktopowa, webowa czy mobilna, nie by&#x142;oby dobrym pomys&#x142;em zmuszanie u&#x17C;ytkownika do czekania a&#x17C; wspomniany wy&#x17C;ej raport si&#x119; wygeneruje. Lepszym sposobem by&#x142;oby umo&#x17C;liwienie u&#x17C;ytkownikowi kontynuowanie korzystania z aplikacji po uprzednim wrzuceniu generowania raportu w t&#x142;o czyli wykonanie tego procesu asynchronicznie. Nast&#x119;pnie po uko&#x144;czeniu tego procesu powiadomienie u&#x17C;ytkownika o rezultacie i mo&#x17C;liwo&#x15B;ci pobrania raportu.</p><p>Typowym technicznym rozwi&#x105;zaniem tego problemu jest skorzystanie z kolejki, gdzie g&#x142;&#xF3;wny system przyjmuj&#x105;cy &#x17C;&#x105;dania od u&#x17C;ytkownika wrzuca zdarzenia na kolejk&#x119; a system raportowy je odbiera i podejmuje odpowiednie dzia&#x142;ania. Dzi&#x119;ki temu, &#x17C;e wykonamy to zadanie asynchronicznie, odci&#x105;&#x17C;ymy nasz g&#x142;&#xF3;wny mikroserwis udost&#x119;pniaj&#x105;cy <a href="https://pl.wikipedia.org/wiki/Interfejs_programowania_aplikacji?ref=lsdev.pl">API</a>, kt&#xF3;re powinno dzia&#x142;a&#x107; szybko i niezawodnie - nieobci&#x105;&#x17C;one przez jakie&#x15B; &quot;ci&#x119;&#x17C;kie&quot; zadania, kt&#xF3;re dzia&#x142;aj&#x105; w tle.</p><h2 id="java-async">Java / Async</h2><p>W tej sekcji skupimy si&#x119; na tym jakie mamy mo&#x17C;liwo&#x15B;ci przetwarzania asynchronicznego w ekosystemie Java. Sprawd&#x17A;my najpierw jakie mamy mo&#x17C;liwo&#x15B;ci pomijaj&#x105;&#x107; wykorzystanie zewn&#x119;trznych bibliotek i framework&#xF3;w.</p><p>Pierwszym co mo&#x17C;e przyj&#x15B;&#x107; do g&#x142;owy jest u&#x17C;ycie w&#x105;tk&#xF3;w.</p><!--kg-card-begin: html--><pre><code class="language-java line-numbers">public static void main(String[] args) throws InterruptedException {
        var thread = new Thread(() -&gt; {
                System.out.println(&quot;Process something in the background&quot;);
        });
        thread.start();
        thread.join();
}
</code></pre><!--kg-card-end: html--><p>Powy&#x17C;sze rozwi&#x105;zanie, mimo &#x17C;e proste, ma istotne wady. Tworzenie w&#x105;tk&#xF3;w jest kosztowne pod wzgl&#x119;dem zasob&#xF3;w, wi&#x119;c tworzenie ich na &#x17C;&#x105;danie raczej nie jest dobrym pomys&#x142;em. Poza tym nie mamy kontroli nad ich ilo&#x15B;ci&#x105;, wi&#x119;c w &#x142;atwy spos&#xF3;b mogliby&#x15B;my wysyci&#x107; nasz system z zasob&#xF3;w.</p><p>Rozwi&#x105;zaniem jest pula w&#x105;tk&#xF3;w, kt&#xF3;ra mo&#x17C;e zwi&#x119;kszy&#x107; wydajno&#x15B;&#x107; naszego systemu i pozwoli&#x107; na nieco wi&#x119;cej kontroli.</p><!--kg-card-begin: html--><pre><code class="language-java line-numbers">public static void main(String[] args) throws InterruptedException {
        var pool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
        pool.execute(() -&gt; {
                System.out.println(&quot;Process something in the background&quot;);
        });
        pool.shutdown();
        pool.awaitTermination(10, TimeUnit.SECONDS);
}
</code></pre><!--kg-card-end: html--><p>Je&#x17C;eli korzystamy z framework&#xF3;w Spring i Spring Boot mo&#x17C;emy wykorzysta&#x107; do tego samego celu klas&#x119; <a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/task/TaskExecutor.html?ref=lsdev.pl"><em>TaskExecutor</em></a>.</p><!--kg-card-begin: html--><pre><code class="language-java line-numbers">@Component
public class Foo {

    private final TaskExecutor taskExecutor;

    public Foo(TaskExecutor taskExecutor) {
        this.taskExecutor = taskExecutor;
    }

    public void bar() {
        taskExecutor.execute(() -&gt; {
            System.out.println(&quot;Process something in the background&quot;);
        });
    }
}
</code></pre><!--kg-card-end: html--><p>Implementacja tej klasy jest dostarczana dzi&#x119;ki autokonfiguracji w <em><a href="https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfiguration.html?ref=lsdev.pl">TaskExecutionAutoConfiguration</a>. </em>Domy&#x15B;lnie na starcie otrzymamy pul&#x119; 8 w&#x105;tk&#xF3;w, kt&#xF3;ra mo&#x17C;e rosn&#x105;&#x107; lub male&#x107; w zale&#x17C;no&#x15B;ci od obci&#x105;&#x17C;enia (ilo&#x15B;&#x107; zada&#x144; w kolejce).</p><p>Konfiguracj&#x119; mo&#x17C;emy w &#x142;atwy spos&#xF3;b <em>tuningowa&#x107;</em>.</p><!--kg-card-begin: html--><pre><code class="language-bash line-numbers">spring:
  task:
    execution:
      pool:
        max-size: 16
        queue-capacity: 100
        keep-alive: &quot;10s&quot;
</code></pre><!--kg-card-end: html--><p>Powy&#x17C;sza konfiguracja ustawia maksymaln&#x105; liczb&#x119; w&#x105;tk&#xF3;w w puli na 16, w zwi&#x105;zku z tym je&#x17C;eli kolejka zada&#x144; do wykonania zape&#x142;ni si&#x119; (100 zada&#x144;), pula w&#x105;tk&#xF3;w zwi&#x119;kszy si&#x119; do 16 w&#x105;tk&#xF3;w. Z kolei je&#x17C;eli w&#x105;tki stan&#x105; si&#x119; bezczynne minimum przez 10 sekund, zostan&#x105; wyrzucone z puli (w przeciwie&#x144;stwie do domy&#x15B;lnej warto&#x15B;ci 60 sekund).</p><p>Mo&#x17C;emy r&#xF3;wnie&#x17C; r&#x119;cznie skonfigurowa&#x107; ten mechanizm dostarczaj&#x105;c kawa&#x142;ek w&#x142;asnej klasy konfiguracyjnej.</p><!--kg-card-begin: html--><pre><code class="language-java line-numbers">@Configuration
public class AppConfig {

    @Bean
    public TaskExecutor taskExecutor() {
        var taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(4);
        taskExecutor.setMaxPoolSize(16);
        taskExecutor.setQueueCapacity(100);
        taskExecutor.setThreadNamePrefix(&quot;task-&quot;);
        return taskExecutor;
    }
}
</code></pre><!--kg-card-end: html--><p>Spring dostarcza r&#xF3;wnie&#x17C; adnotacj&#x119; <em><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/annotation/Async.html?ref=lsdev.pl">@Async</a>, </em>dzi&#x119;ki kt&#xF3;rej w &#x142;atwy spos&#xF3;b mo&#x17C;emy wywo&#x142;ywa&#x107; dowoln&#x105; metod&#x119; asynchronicznie korzystaj&#x105;c z puli w&#x105;tk&#xF3;w, kt&#xF3;r&#x105; wcze&#x15B;niej utworzyli&#x15B;my.</p><p>Mechanizm ten trzeba najpierw w&#x142;&#x105;czy&#x107; adnotacj&#x105; <em><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/annotation/EnableAsync.html?ref=lsdev.pl">@EnableAsync</a> </em>co spowoduje zaimportowanie odpowiedniej autokonfiguracji.</p><!--kg-card-begin: html--><pre><code class="language-java line-numbers">@Configuration
@EnableAsync
public class AppConfig {

    @Bean
    public TaskExecutor taskExecutor() {
        var taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(4);
        taskExecutor.setMaxPoolSize(16);
        taskExecutor.setQueueCapacity(100);
        taskExecutor.setThreadNamePrefix(&quot;task-&quot;);
        return taskExecutor;
    }

    @Bean
    public Clock clock() {
        return Clock.system(ZoneId.of(&quot;Europe/Warsaw&quot;));
    }
}
</code></pre><!--kg-card-end: html--><p>Nast&#x119;pnie zamiast r&#x119;cznie wstrzykiwa&#x107; TaskExecutor wykorzystamy wspomnian&#x105; adnotacj&#x119; <em><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/annotation/Async.html?ref=lsdev.pl">@Async</a>.</em></p><!--kg-card-begin: html--><pre><code class="language-java line-numbers">@Slf4j
@Component
public class Foo {

    @Async
    public void bar() {
        log.info(&quot;Hello world&quot;);
    }

    @Async
    public Future<string> xyz() {
        return new AsyncResult&lt;&gt;(&quot;Hello world as a result&quot;);
    }
}

@SpringBootApplication
public class DemoApplication {

        public static void main(String[] args) {
                SpringApplication.run(DemoApplication.class, args);
        }

        @Autowired
        private Foo foo;

        @EventListener(ApplicationReadyEvent.class)
        public void ready() throws ExecutionException, InterruptedException {
                foo.bar();
                String result = foo.xyz().get();
                System.out.println(result);
        }
}
</string></code></pre><!--kg-card-end: html--><h2 id="scheduler">Scheduler</h2><p>Wiemy ju&#x17C; w jaki spos&#xF3;b mo&#x17C;emy wrzuca&#x107; zadania w t&#x142;o. W tej sekcji po&#x15B;wi&#x119;cimy czas jak takie zadania mo&#x17C;emy planowa&#x107; i wykonywa&#x107; w przysz&#x142;o&#x15B;ci jednorazowo lub cyklicznie.</p><p>Implementowanie takiego mechanizmu samodzielnie by&#x142;oby do&#x15B;&#x107; czasoch&#x142;onne dlatego b&#x119;dziemy posi&#x142;kowa&#x107; si&#x119; tym co dostarcza nam Spring. Istniej&#x105; r&#xF3;wnie&#x17C; zewn&#x119;trzne alternatywy tj. <a href="https://www.quartz-scheduler.org/?ref=lsdev.pl">Quartz</a>, kt&#xF3;rymi mo&#x17C;esz si&#x119; zainteresowa&#x107;.</p><p>Analogicznie jak w przypadku <em><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/task/TaskExecutor.html?ref=lsdev.pl">TaskExecutor</a> </em>w celu planowania zada&#x144; mamy do dyspozycji klas&#x119; <em><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/TaskScheduler.html?ref=lsdev.pl">TaskScheduler</a>. </em>Spring dzi&#x119;ki autokonfiguracji w klasie <em><a href="https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfiguration.html?ref=lsdev.pl">TaskSchedulingAutoConfiguration</a></em> dostarcza nam implementacj&#x119; tej klasy.</p><!--kg-card-begin: html--><pre><code class="language-java line-numbers">@SpringBootApplication
public class DemoApplication {

        public static void main(String[] args) {
                SpringApplication.run(DemoApplication.class, args);
        }

        @Autowired
        private MailSender mailSender;

        @Autowired
        private TaskScheduler taskScheduler;

        @EventListener(ApplicationReadyEvent.class)
        public void ready() {
                // 1
                taskScheduler.schedule(() -&gt; {
                        mailSender.sendMail();
                }, Instant.now().plusSeconds(3600));

                // 2
                taskScheduler.scheduleAtFixedRate(() -&gt; {
                        System.out.println(&quot;Hello world&quot;);
                }, 1000);
        }
}
</code></pre><!--kg-card-end: html--><p>Powy&#x17C;szy przyk&#x142;ad pokazuje jak mo&#x17C;emy dynamicznie zaplanowa&#x107; jednorazowe zadanie do wykonania na p&#xF3;&#x17A;niej, w tym przypadku wysy&#x142;ka maila po up&#x142;yni&#x119;ciu godziny a tak&#x17C;e zadanie wykonywane cyklicznie.</p><p>Spring dostarcza nam r&#xF3;wnie&#x17C; adnotacj&#x119; <em><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/annotation/Scheduled.html?ref=lsdev.pl">@Scheduled</a>, </em>dzi&#x119;ki kt&#xF3;rej w &#x142;atwy spos&#xF3;b mo&#x17C;emy wyrazi&#x107; w jakich okoliczno&#x15B;ciach nasze job&apos;y maj&#x105; by&#x107; uruchamiane i wykonywane. Mechanizm ten trzeba najpierw w&#x142;&#x105;czy&#x107; adnotacj&#x105; <em><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/annotation/EnableScheduling.html?ref=lsdev.pl">@EnableScheduling</a></em> co spowoduje zaimportowanie odpowiedniej autokonfiguracji.</p><!--kg-card-begin: html--><pre><code class="language-java line-numbers">@Configuration
@EnableAsync
@EnableScheduling
public class AppConfig {

    @Bean
    public Clock clock() {
        return Clock.system(ZoneId.of(&quot;Europe/Warsaw&quot;));
    }
}
</code></pre><!--kg-card-end: html--><p>Nast&#x119;pnie mo&#x17C;emy przej&#x15B;&#x107; do stworzenia przyk&#x142;adowego job&apos;a.</p><!--kg-card-begin: html--><pre><code class="language-java line-numbers">@Slf4j
@Component
public class JobExample {

    @Scheduled(fixedRate = 1000)
    public void hello() {
        log.info(&quot;Hello world&quot;);
    }
}
</code></pre><!--kg-card-end: html--><p>W tym przypadku po prostu wypisujemy na konsol&#x119; &apos;Hello world&apos; co sekund&#x119;. Powy&#x17C;sza adnotacja ma wi&#x119;cej parametr&#xF3;w:</p><ul><li>zone - strefa czasowa, wed&#x142;ug kt&#xF3;rej ma zosta&#x107; uruchomione zadanie</li><li>initialDelay - op&#xF3;&#x17A;nienie pierwszego uruchomienia w milisekundach (jednostk&#x119; mo&#x17C;na zmieni&#x107; parametrem timeUnit)</li><li>fixedDelay - czas pomi&#x119;dzy zako&#x144;czeniem ostatniego wykonania a rozpocz&#x119;ciem kolejnego (wykorzystujemy do zada&#x144; zale&#x17C;nych od siebie)</li><li>fixedRate - czas pomi&#x119;dzy rozpocz&#x119;ciem ostatniego i kolejnego wykonania (wykorzystujemy do zada&#x144; nieposiadaj&#x105;cych zasob&#xF3;w wsp&#xF3;&#x142;dzielonych mi&#x119;dzy sob&#x105;)</li><li>fixedDelayString - liczba w formacie zrozumia&#x142;ym przez Duration np. PT48H</li><li>cron - wyra&#x17C;enie cron w postaci ci&#x105;gu znak&#xF3;w<br>&quot;a b c d e f&quot; (cron expression)<br>a - sekunda (0-59) b - minuta (0-59) c - godzina (0-23) d - dzien msca (1-31) e - miesiac (1-12) f - dzien tygodnia<br><strong>przyk&#x142;ady</strong>:<br>9 12 * * * &#x2013; co sekund&#x119; przez minut&#x119; od godz. 12:09:00 ka&#x17C;dego dnia<br>0 0 2-4 * * * &#x2013; godz.2:00:00, 3:00:00 i 4:00:00 ka&#x17C;dego dnia<br>0 * 6,19 * * 2 &#x2013; 6:00:00 i 19:00:00 w ka&#x17C;dy wtorek<br>0 0/30 10 * JAN * &#x2013; 10:00:00, 10:30:00 ka&#x17C;dego dnia stycznia<br>0 0 12 * * MON-FRI &#x2013; 12:00:00 od poniedzia&#x142;ku do pi&#x105;tku<br>0 0 0 3 5 ? &#x2013; ka&#x17C;dego 3 maja o p&#xF3;&#x142;nocy</li></ul><p>Istnieje mo&#x17C;liwo&#x15B;&#x107; jednej metodzie przypisa&#x107; wiele wywo&#x142;a&#x144; cyklicznych, kt&#xF3;re zgrupowane tworz&#x105; harmonogram. Nale&#x17C;y do tego wykorzysta&#x107; adnotacje <em><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/annotation/Schedules.html?ref=lsdev.pl">@Schedules</a> </em>i w jej argumencie przekaza&#x107; zadania do wykonania.</p><!--kg-card-begin: html--><pre><code class="language-java line-numbers">@Schedules({
        @Scheduled(cron = &quot;0 30 10-13 ? * WED,FRI&quot;),
        @Scheduled(fixedDelay = 10000)
})
</code></pre><!--kg-card-end: html--><p>Warto pami&#x119;ta&#x107; o nast&#x119;puj&#x105;cych regu&#x142;ach zwi&#x105;zanych z metod&#x105; pod adnotacj&#x105; <em><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/annotation/Scheduled.html?ref=lsdev.pl">@Scheduled</a></em>:</p><ul><li>powinna zwraca&#x107; <em><strong>void</strong></em></li><li>nie powinna mie&#x107; &#x17C;adnych parametr&#xF3;w w innym przypadku zostanie rzucony wyj&#x105;tek typu <em>IllegalStateException</em></li></ul><h3 id="wielko%C5%9B%C4%87-puli">Wielko&#x15B;&#x107; puli</h3><p>Domy&#x15B;lnie w celu wykonania zada&#x144; Spring tworzy pul&#x119; w&#x105;tk&#xF3;w o wielko&#x15B;ci 1. W zwi&#x105;zku z tym, je&#x17C;eli uruchomimy sekwencj&#x119; kilku niezale&#x17C;nych zada&#x144; u&#x17C;ywaj&#x105;c parametru <em>fixedRate</em>, implementacja zadzia&#x142;a podobnie jak <em>fixedDelay </em>poniewa&#x17C; w tym przypadku w jednym momencie tylko jedno zadanie mo&#x17C;e by&#x107; uruchomione.</p><p>Mo&#x17C;emy w prosty spos&#xF3;b zmieni&#x107; liczb&#x119; w&#x105;tk&#xF3;w w puli aby zmieni&#x107; to zachowanie dodaj&#x105;c odpowiedni&#x105; konfiguracj&#x119;.</p><!--kg-card-begin: html--><pre><code class="language-bash line-numbers">spring:
  task:
    scheduling:
      thread-name-prefix: &quot;scheduling-&quot;
      pool:
        size: 2
</code></pre><!--kg-card-end: html--><p>Oczywi&#x15B;cie nic nie stoi na przeszkodzie aby dostarczy&#x107; dla frameworka w&#x142;asn&#x105; implementacj&#x119;.</p><!--kg-card-begin: html--><pre><code class="language-java line-numbers">@Configuration
public class AppConfig {

    @Bean
    public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
        ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
        threadPoolTaskScheduler.setPoolSize(4);
        threadPoolTaskScheduler.setThreadNamePrefix(&quot;scheduling-&quot;);
        return threadPoolTaskScheduler;
    }
}
</code></pre><!--kg-card-end: html--><h2 id="synchronizacja">Synchronizacja</h2><p>Zazwyczaj je&#x17C;eli system robi jakie&#x15B; cykliczne operacje np. wysy&#x142;anie maila do klienta z faktur&#x105; raz na miesi&#x105;c, raczej chcieliby&#x15B;my unikn&#x105;&#x107; wielokrotnego wysy&#x142;ania tych samych rzeczy (chyba, &#x17C;e mieli&#x15B;my jakie&#x15B; problemy infrastrukturalne i chcemy ponowi&#x107; wysy&#x142;k&#x119;). Problem ten mo&#x17C;e powsta&#x107; je&#x17C;eli nasza aplikacja jest uruchomiona na kilku instancjach. Na szcz&#x119;&#x15B;cie istniej&#x105; rozwi&#x105;zania tego problemu. Mo&#x17C;emy np. skorzysta&#x107; z biblioteki <em><a href="https://github.com/lukas-krecan/ShedLock?ref=lsdev.pl">ShedLock</a></em>, kt&#xF3;ra pozwala na synchronizacj&#x119; zada&#x144;. Kolejnym rozwi&#x105;zaniem mo&#x17C;e by&#x107; wydzielenie funkcjonalno&#x15B;ci odpowiedzialnych za harmonogram zada&#x144; i planowanie do osobnego mikroserwisu i upewnienie si&#x119;, &#x17C;e w klastrze istnieje tylko jedna jego instancja.</p><h2 id="praktyczne-przyk%C5%82ady">Praktyczne przyk&#x142;ady</h2><p>We&#x17A;my na warsztat 2 przyk&#x142;ady.</p><p>Za&#x142;&#xF3;&#x17C;my, &#x17C;e mamy aplikacj&#x119;, kt&#xF3;ra dzia&#x142;a w modelu subskrypcyjnym (np. Spotify). Obci&#x105;&#x17C;amy u&#x17C;ytkownika co miesi&#x105;c za oferowane us&#x142;ugi. Jak technicznie podej&#x15B;&#x107; do tego tematu? W ko&#x144;cu ka&#x17C;dy u&#x17C;ytkownik m&#xF3;g&#x142; wykupi&#x107; abonament dowolnego dnia miesi&#x105;ca, wi&#x119;c kolejne pobranie &#x15B;rodk&#xF3;w powinno nast&#x105;pi&#x107; dok&#x142;adnie za miesi&#x105;c. Rozwi&#x105;zaniem mo&#x17C;e by&#x107; przygotowanie joba, kt&#xF3;ry codziennie b&#x119;dzie si&#x119; &quot;budzi&#x142;&quot; i przed&#x142;u&#x17C;a&#x142; subskrypcj&#x119; wszystkim u&#x17C;ytkownikom, kt&#xF3;rym us&#x142;uga ju&#x17C; wygas&#x142;a uprzednio pobieraj&#x105;c &#x15B;rodki.</p><p>Innym pomys&#x142;em, kt&#xF3;rego osobi&#x15B;cie nie testowa&#x142;em mog&#x142;oby by&#x107; skorzystanie z op&#xF3;&#x17A;niania wiadomo&#x15B;ci na kolejce u&#x17C;ywaj&#x105;c np. <em><a href="https://github.com/rabbitmq/rabbitmq-delayed-message-exchange?ref=lsdev.pl">RabbitMq delayed message exchange</a> </em>lub innego podobnego mechanizmu.</p><p>Kolejny przyk&#x142;ad. Wyobra&#x17A;my sobie, &#x17C;e chcemy wysy&#x142;a&#x107; powiadomienia sms/mail/push do wszystkich u&#x17C;ytkownik&#xF3;w w naszym systemie o godzinie 10 rano bior&#x105;c pod uwag&#x119; to, &#x17C;e u&#x17C;ytkownicy mog&#x105; by&#x107; w r&#xF3;&#x17C;nych strefach czasowych (wi&#x119;c nie chcieliby&#x15B;my budzi&#x107; niekt&#xF3;rych o 3 w nocy). Jak mogliby&#x15B;my podej&#x15B;&#x107; do tematu? Jednym z rozwi&#x105;za&#x144; mo&#x17C;e by&#x107; job uruchamiany co godzin&#x119;, kt&#xF3;ry za ka&#x17C;dym razem wysy&#x142;a&#x142;by powiadomienie tylko do u&#x17C;ytkownik&#xF3;w, u kt&#xF3;rych aktualnie jest godzina 10. Oczywi&#x15B;cie pami&#x119;taj&#x105;c o przechowywaniu strefy czasowej u&#x17C;ytkownika w bazie danych aby m&#xF3;c stwierdzi&#x107; wspomniany warunek.</p><h2 id="testowanie">Testowanie</h2><p>Za&#x142;&#xF3;&#x17C;my, &#x17C;e mamy implementacj&#x119; joba, kt&#xF3;ry codziennie od poniedzia&#x142;ku do pi&#x105;tku o godzinie 12 wysy&#x142;a maila.</p><!--kg-card-begin: html--><pre><code class="language-java line-numbers">@Component
public class MailSenderJob {

    private final MailSender mailSender;

    public MailSenderJob(MailSender mailSender) {
        this.mailSender = mailSender;
    }

    @Scheduled(cron = &quot;0 0 12 * * MON-FRI&quot;, zone = &quot;Europe/Warsaw&quot;)
    public void sendMail() {
        mailSender.sendMail(&quot;Hello world&quot;);
    }
}
</code></pre><!--kg-card-end: html--><p>W jaki spos&#xF3;b mo&#x17C;emy przetestowa&#x107; tak&#x105; implementacj&#x119;? Adnotacja <em><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/annotation/Scheduled.html?ref=lsdev.pl">@Scheduled</a> </em>dzia&#x142;a tylko w porozumieniu z springiem, wi&#x119;c koniecznym b&#x119;dzie podniesienie kontekstu springowego w testach tj. przygotowanie testu integracyjnego.</p><p>Zobaczmy najpierw jak mo&#x17C;e wygl&#x105;da&#x107; poprawna konfiguracja schedulera.</p><!--kg-card-begin: html--><pre><code class="language-java line-numbers">@Configuration
@EnableScheduling
public class SchedulerConfig {

    @Bean
    public ThreadPoolTaskScheduler threadPoolTaskScheduler(Clock clock) {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        taskScheduler.setPoolSize(5);
        taskScheduler.setClock(clock);
        taskScheduler.setWaitForTasksToCompleteOnShutdown(true);
        taskScheduler.setThreadNamePrefix(&quot;scheduling-&quot;);
        return taskScheduler;
    }

    @Profile(&quot;!it&quot;)
    @Bean
    public Clock clock() {
        return Clock.system(ZoneId.of(&quot;Europe/Warsaw&quot;));
    }
}
</code></pre><!--kg-card-end: html--><p>Jak wida&#x107; w powy&#x17C;szym przyk&#x142;adzie <em>taskScheduler </em>tworzony jest na podstawie wstrzykni&#x119;tego obiektu klasy <em>Clock</em>. Dzi&#x119;ki temu w testach b&#x119;dziemy mogli w &#x142;atwy spos&#xF3;b podmieni&#x107; t&#x105; implementacj&#x119; co pozwoli nam na przesuwanie si&#x119; w czasie np. ustawiaj&#x105;c czas kiedy job powinien wystartowa&#x107;, nast&#x119;pnie wykonanie asercji czy faktycznie tak si&#x119; sta&#x142;o. Odsy&#x142;am r&#xF3;wnie&#x17C; do mojego <a href="https://lsdev.pl/posts/przetwarzanie-daty-i-czasu-w-jezyku-java/">poprzedniego</a> artyku&#x142;u gdzie tak&#x17C;e o tym pisa&#x142;em.</p><p>Rozpocznijmy od stworzenia pomocnej implementacji <em>Clock&apos;a</em>.</p><!--kg-card-begin: html--><pre><code class="language-java line-numbers">public class TestClock extends Clock {

    private final Clock baseClock;
    private Instant initializationTime;

    public TestClock(Instant fixed, ZoneId zone) {
        this(Clock.fixed(fixed, zone));
    }

    private TestClock(Clock baseClock) {
        this.baseClock = baseClock;
    }

    @Override
    public ZoneId getZone() {
        return baseClock.getZone();
    }

    @Override
    public Clock withZone(ZoneId zone) {
        return new TestClock(baseClock.withZone(zone));
    }

    @Override
    public long millis() {
        return instant().toEpochMilli();
    }

    @Override
    public Instant instant() {
        synchronized (this) {
            if (initializationTime == null) {
                initializationTime = Instant.now();
            }
        }
        long elapsedTime = ChronoUnit.MILLIS.between(initializationTime, Instant.now());
        return baseClock.instant().plusMillis(elapsedTime);
    }
}
</code></pre><!--kg-card-end: html--><p>Nast&#x119;pnie przetestujmy nasz&#x105; funkcjonalno&#x15B;&#x107;.</p><!--kg-card-begin: html--><pre><code class="language-java line-numbers">@SpringBootTest
@ActiveProfiles(&quot;it&quot;)
class MailSenderJobTest {

    private static final Instant NOW = ZonedDateTime.parse(&quot;2023-04-12T11:59:57.000+02:00[Europe/Warsaw]&quot;).toInstant();

    @SpyBean
    private MailSender mailSender;

    @Test
    void shouldSendMailOnTime() {
        await().atMost(Duration.ofSeconds(5))
                .untilAsserted(() -&gt; verify(mailSender).sendMail(&quot;Hello world&quot;));
    }

    @TestConfiguration
    static class TestConfig {
        @Bean
        public Clock clock() {
            return new TestClock(NOW, ZoneId.of(&quot;Europe/Warsaw&quot;));
        }
    }
}
</code></pre><!--kg-card-end: html--><h2 id="alternatywne-podej%C5%9Bcie">Alternatywne podej&#x15B;cie</h2><p><em>Schedulery</em> s&#x105; silnym narz&#x119;dziem, kt&#xF3;re pomagaj&#x105; w wykonywaniu cyklicznych operacji jednak rodz&#x105; te&#x17C; pewne problemy:</p><ul><li>hardcodowanie konfiguracji</li><li>testowanie nie jest proste</li><li>zmiana cykliczno&#x15B;ci wykonywanego zadania<br>mo&#x17C;e spowodowa&#x107; przebudowanie znacznej cz&#x119;&#x15B;ci aplikacji</li></ul><p>Lepszym rozwi&#x105;zaniem z punktu widzenia utrzymania aplikacji mo&#x17C;e si&#x119; okaza&#x107; utworzenie metody webowej, kt&#xF3;ra b&#x119;dzie przygotowana na wywo&#x142;anie cykliczne. Wtedy &#x15B;rodowisko CI/CD mog&#x142;oby pe&#x142;ni&#x107; rol&#x119; inicjatora i wywo&#x142;ywa&#x107; metod&#x119; cykliczn&#x105; w odpowiednich momentach w zale&#x17C;no&#x15B;ci od konfiguracji.</p><h2 id="podsumowanie">Podsumowanie</h2><p>Mam nadziej&#x119;, &#x17C;e w pewnym stopniu przybli&#x17C;y&#x142;em Tobie jak w praktyce mo&#x17C;emy wykorzystywa&#x107; narz&#x119;dzia dostarczone w ekosystemie Java &amp; Spring zwi&#x105;zane z planowaniem, tak aby u&#x142;atwi&#x107; sobie prac&#x119; a zarazem zrobi&#x107; to poprawnie. Je&#x17C;eli masz pytania lub chcia&#x142;by&#x15B; podzieli&#x107; si&#x119; czym&#x15B; ciekawym zostaw komentarz. Po wi&#x119;cej informacji odsy&#x142;am do <a href="https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/?ref=lsdev.pl#features.task-execution-and-scheduling">dokumentacji</a>. Do nast&#x119;pnego! &#x1F44B;</p>]]></content:encoded></item><item><title><![CDATA[Przetwarzanie daty i czasu w języku Java i pułapki z tym związane]]></title><description><![CDATA[<p>Przetwarzanie daty i czasu w aplikacjach zawsze sprawia&#x142;o programistom wiele trudno&#x15B;ci gdy&#x17C; trzeba wzi&#x105;&#x107; wiele rzeczy pod uwag&#x119;. Wystarczy zerkn&#x105;&#x107; na t&#x119; <a href="https://yourcalendricalfallacyis.com/?ref=lsdev.pl">stron&#x119;</a> aby uzmys&#x142;owi&#x107; sobie ile potencjalnych b&#x142;&#x119;dnych za&#x142;o&</p>]]></description><link>https://lsdev.pl/posts/przetwarzanie-daty-i-czasu-w-jezyku-java/</link><guid isPermaLink="false">636d99f2aa24aed745fc4e16</guid><category><![CDATA[Java]]></category><category><![CDATA[Programming]]></category><dc:creator><![CDATA[Łukasz]]></dc:creator><pubDate>Sun, 13 Nov 2022 03:52:34 GMT</pubDate><media:content url="https://lsdev.pl/content/images/2022/11/daniele-levis-pelusi-Pp9qkEV_xPk-unsplash.webp" medium="image"/><content:encoded><![CDATA[<img src="https://lsdev.pl/content/images/2022/11/daniele-levis-pelusi-Pp9qkEV_xPk-unsplash.webp" alt="Przetwarzanie daty i czasu w j&#x119;zyku Java i pu&#x142;apki z tym zwi&#x105;zane"><p>Przetwarzanie daty i czasu w aplikacjach zawsze sprawia&#x142;o programistom wiele trudno&#x15B;ci gdy&#x17C; trzeba wzi&#x105;&#x107; wiele rzeczy pod uwag&#x119;. Wystarczy zerkn&#x105;&#x107; na t&#x119; <a href="https://yourcalendricalfallacyis.com/?ref=lsdev.pl">stron&#x119;</a> aby uzmys&#x142;owi&#x107; sobie ile potencjalnych b&#x142;&#x119;dnych za&#x142;o&#x17C;e&#x144; mo&#x17C;emy pope&#x142;ni&#x107;. Mnogo&#x15B;&#x107; r&#xF3;&#x17C;nych format&#xF3;w zapisu daty i czasu, strefy czasowe, zmiany czasu z letniego na zimowy i odwrotnie oraz inne tego typu aspekty powoduj&#x105;, &#x17C;e &#x142;atwo jest strzeli&#x107; sobie w stop&#x119;. Na domiar z&#x142;ego wszystko komplikuje ekosystem Javy, kt&#xF3;ra udost&#x119;pnia kilka r&#xF3;&#x17C;nych interfejs&#xF3;w i mechanizm&#xF3;w operacji na datach, przez co szczeg&#xF3;lnie pocz&#x105;tkuj&#x105;cy mog&#x105; poczu&#x107; si&#x119; zagubieni i nie&#x15B;wiadomi potencjalnych problem&#xF3;w.</p><p>W tym artykule wyja&#x15B;ni&#x119; w jaki spos&#xF3;b przetwarza&#x107;, zapisywa&#x107; i testowa&#x107; czas w j&#x119;zyku Java przy okazji wspominaj&#x105;c o potencjalnych problemach, kt&#xF3;re mo&#x17C;emy napotka&#x107;.</p><h2 id="legacy-api">Legacy Api</h2><p>W j&#x119;zyku Java mamy do dyspozycji wiele klas i interfejs&#xF3;w wspieraj&#x105;cych przetwarzanie dat i czasu. Gdy Java wysz&#x142;a na &#x15B;wiat w pierwszej wersji otrzymali&#x15B;my klas&#x119; <em>java.util.Date</em>, kt&#xF3;ra reprezentuj&#x119; punkt w czasie z milisekundow&#x105; dok&#x142;adno&#x15B;ci&#x105;.</p><!--kg-card-begin: html--><pre><code class="language-java line-numbers">Date now = new Date();
Date tomorrow = new Date(now.getTime() + 1000 * 3600 * 24);
System.out.println(now.getMonth() + 1); // print month
System.out.println(now.getYear() + 1900); // print year
if (now.before(tomorrow)) {
	System.out.println(&quot;now is before tomorrow&quot;);
}
if (tomorrow.after(now)) {
	System.out.println(&quot;tomorrow is after now&quot;);
}
if (now.compareTo(now) == 0) {
	System.out.println(&quot;now is equal to now&quot;);
}
</code></pre><!--kg-card-end: html--><p>Jak wida&#x107; powy&#x17C;ej API nie jest intuicyjne i nie nale&#x17C;y do najprostszych. Wyci&#x105;ganie takich sk&#x142;adowych jak rok czy miesi&#x105;c lub formatowanie jest niepor&#x119;czne nie wspominaj&#x105;c o internacjonalizacji czy strefach czasowych. Obecnie wi&#x119;kszo&#x15B;&#x107; metod tej klasy jest oznaczona jako <em>@Deprecated</em> a sama klasa nie jest zalecana do u&#x17C;ycia.</p><p>Rozwi&#x105;zaniem powy&#x17C;szych problem&#xF3;w mia&#x142;a by&#x107; klasa <em>Calendar</em>, kt&#xF3;r&#x105; otrzymali&#x15B;my w wersji 1.1 wraz z klasami odpowiedzialnymi za formatowanie (<em>DateFormat</em>, <em>SimpleDateFormat</em>) i strefy czasowe (<em>TimeZone</em>, <em>SimpleTimeZone</em>).</p><!--kg-card-begin: html--><pre><code class="language-java line-numbers">// Calendar with default timzone
Calendar calendar = Calendar.getInstance();

// Calendar with specific timezone
Calendar calendarWithZone = Calendar.getInstance(TimeZone.getTimeZone(&quot;Europe/Warsaw&quot;));

// get date object
Date now = calendar.getTime();

// set time object
Date later = new Date();
calendar.setTime(later);

// get date components
int year = calendar.get(Calendar.YEAR);
int month = calendar.get(Calendar.MONTH);
int day = calendar.get(Calendar.DAY_OF_MONTH);
int hour = calendar.get(Calendar.HOUR);
int minute = calendar.get(Calendar.MINUTE);
int second = calendar.get(Calendar.SECOND);
int millis = calendar.get(Calendar.MILLISECOND);

// add 24 hours
calendar.add(Calendar.HOUR, 24);

// Internationalization
Locale locale = new Locale(&quot;pl&quot;, &quot;PL&quot;);
DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.FULL, locale);
System.out.println(dateFormat.format(later)); // czwartek, 3 listopada 2022
</code></pre><!--kg-card-end: html--><p>API jest bardziej czytelne i intuicyjne jednak obecnie r&#xF3;wnie&#x17C; niezalecane do wykorzystywania. Interfejs by&#x142; projektowany gdy w specyfikacji j&#x119;zyka nie by&#x142;o jeszcze typu wyliczeniowego wi&#x119;c &#x142;atwo o b&#x142;&#x105;d w przypadku sta&#x142;ych numerycznych - nie mo&#x17C;emy liczy&#x107; na b&#x142;&#x105;d kompilacji. Co wi&#x119;cej klasa <em>Calendar</em> jest narzutem na wydajno&#x15B;&#x107; i zu&#x17C;ywa sporo pami&#x119;ci poniewa&#x17C; wewn&#x119;trznie przechowuje stan na dwa r&#xF3;&#x17C;ne sposoby.</p><p>Kolejnym problemem dla om&#xF3;wionych powy&#x17C;ej API jest bezpiecze&#x144;stwo w przypadku przetwarzania wsp&#xF3;&#x142;bie&#x17C;nego tj. na wielu w&#x105;tkach. Z uwagi na to, &#x17C;e klasy <em>Date</em> jak i <em>Calendar</em> mog&#x105; zmienia&#x107; stan (mutable) &#x142;atwo o b&#x142;&#x105;d gdy dojdzie do wy&#x15B;cigu w zwi&#x105;zku z tym niezb&#x119;dna jest synchronizacja lub u&#x17C;ywanie mechanizmu <em><a href="https://docs.oracle.com/javase/7/docs/api/java/lang/ThreadLocal.html?ref=lsdev.pl">ThreadLocal</a></em>.</p><p>Istniej&#x105; r&#xF3;wnie&#x17C; klasy rozszerzaj&#x105;ce klas&#x119; <em>java.util.Date</em> (wrappery) m.in. <em>java.sql.Date</em>, <em>java.sql.Time</em>, <em>java.sql.Timestamp</em> stworzone na potrzeby <em><a href="https://docs.oracle.com/javase/8/docs/technotes/guides/jdbc/?ref=lsdev.pl">jdbc-api</a>. </em>Maj&#x105; one zastosowanie gdy integrujemy si&#x119; z baz&#x105; danych w zwi&#x105;zku z tym nie powinni&#x15B;my ich u&#x17C;ywa&#x107; w kodzie domenowym a jedynie do odczytu/zapisu czy te&#x17C; mapowania relacyjno-obiektowego.</p><h2 id="nowe-api-od-java-8">Nowe API od Java 8</h2><p>W wersji 8 doczekali&#x15B;my si&#x119; nowego API zwi&#x105;zanego z obs&#x142;ug&#x105; dat i czasu kojarzone r&#xF3;wnie&#x17C; jako <a href="https://jcp.org/en/jsr/detail?id=310&amp;ref=lsdev.pl">JSR-310</a>. Nowa specyfikacja jest o wiele bardziej intuicyjna i pozwala zast&#x105;pi&#x107; stare API om&#xF3;wione w poprzednim punkcie jak i inne zewn&#x119;trzne biblioteki usprawniaj&#x105;ce prace z czasem np.<a href="https://www.joda.org/joda-time?ref=lsdev.pl"> joda-time</a> - najcz&#x119;&#x15B;ciej u&#x17C;ywana przed wydaniem tej wersji.</p><p>Nowe API zosta&#x142;o usytuowane w pakiecie <em>java.time </em>i zawiera m.in. nast&#x119;puj&#x105;ce klasy:</p><p><strong><a href="https://docs.oracle.com/javase/8/docs/api/java/time/Instant.html?ref=lsdev.pl">Insant</a> - </strong>Reprezentuje okre&#x15B;lony punkt w czasie z nanosekundow&#x105; precyzj&#x105;.</p><p><strong><a href="https://docs.oracle.com/javase/8/docs/api/java/time/Duration.html?ref=lsdev.pl">Duration</a> - </strong>Odcinek czasu z nanosekundow&#x105; precyzj&#x105; np. 34.5 sekundy.</p><p><strong><a href="https://docs.oracle.com/javase/8/docs/api/java/time/LocalDateTime.html?ref=lsdev.pl">LocalDateTime</a> - </strong>Data i czas bez strefy czasowej w standardzie ISO-8601 z nanosekundow&#x105; precyzj&#x105;<br>np. 2022-12-03T14:15:30.</p><p><strong><a href="https://docs.oracle.com/javase/8/docs/api/java/time/LocalDate.html?ref=lsdev.pl">LocalDate</a> - </strong>Data bez strefy czasowej w standardzie ISO-8601 np. 2022-10-03.</p><p><strong><a href="https://docs.oracle.com/javase/8/docs/api/java/time/LocalTime.html?ref=lsdev.pl">LocalTime</a> - </strong>Czas bez strefy czasowej w standardzie ISO-8601 z nanosekundow&#x105; precycj&#x105; np. 12:30:45.</p><p><strong><a href="https://docs.oracle.com/javase/8/docs/api/java/time/ZonedDateTime.html?ref=lsdev.pl">ZonedDateTime</a> - </strong>Data i czas z stref&#x105; czasow&#x105; w standardzie ISO-8601 z nanosekundow&#x105; precyzj&#x105;<br>np. 2022-11-14T14:15:30+01:00 Europe/Warsaw.</p><p><strong><a href="https://docs.oracle.com/javase/8/docs/api/java/time/ZoneId.html?ref=lsdev.pl">ZoneId</a> - </strong>Identyfikator strefy czasowej np. Europe/Warsaw.</p><p><strong><a href="https://docs.oracle.com/javase/8/docs/api/java/time/Year.html?ref=lsdev.pl">Year</a> - </strong>Rok w standardzie ISO-8601 np. 2022.</p><p><strong><a href="https://docs.oracle.com/javase/8/docs/api/java/time/MonthDay.html?ref=lsdev.pl">MonthDay</a> - </strong>Miesi&#x105;c i dzie&#x144; w standardzie ISO-8601 np. 11-28.</p><p><strong><a href="https://docs.oracle.com/javase/8/docs/api/java/time/YearMonth.html?ref=lsdev.pl">YearMonth</a> - </strong>Rok i miesi&#x105;c w standardzie ISO-8601 np. 2022-12.</p><p><strong><a href="https://docs.oracle.com/javase/8/docs/api/java/time/ZoneOffset.html?ref=lsdev.pl">ZoneOffset</a> - </strong>Offset od czasu Greenwich/UTC z sekundow&#x105; precyzj&#x105; np. +02:00.</p><p><strong><a href="https://docs.oracle.com/javase/8/docs/api/java/time/OffsetDateTime.html?ref=lsdev.pl">OffsetDateTime</a> - </strong>Data i czas z przesuni&#x119;ciem od czasu Greenwich/UTC w standardzie ISO-8601<br>z nanosekundow&#x105; precyzj&#x105; np. 2022-11-06T12:15:30+01:00.</p><p><strong><a href="https://docs.oracle.com/javase/8/docs/api/java/time/OffsetTime.html?ref=lsdev.pl">OffsetTime</a> - </strong>Czas z przesuni&#x119;ciem od czasu Greenwich/UTC w standardzie ISO-8601<br>z nanosekundow&#x105; precyzj&#x105; np. 11:15:30+01:00.</p><p><strong><a href="https://docs.oracle.com/javase/8/docs/api/java/time/Period.html?ref=lsdev.pl">Period</a> - </strong>Odcinek czasu jako ilo&#x15B;&#x107; lat, miesi&#x119;cy i dni w standardzie ISO-8601 np.<br>&apos;2 years, 3 months and 4 days&apos;.</p><p><strong><a href="https://docs.oracle.com/javase/8/docs/api/java/time/Clock.html?ref=lsdev.pl">Clock</a> - </strong>Pozwala na dost&#x119;p do aktualnego czasu i strefy czasowej.</p><p>Klas jest do&#x15B;&#x107; sporo wi&#x119;c na pocz&#x105;tku mo&#x17C;e by&#x107; trudno si&#x119; zorientowa&#x107;, kt&#xF3;rych u&#x17C;y&#x107; w jakim przypadku natomiast niebagateln&#x105; zalet&#x105; tego jest to, &#x17C;e ka&#x17C;da klasa ma swoje przeznaczenie zgodnie z zasad&#x105; <a href="https://en.wikipedia.org/wiki/Single-responsibility_principle?ref=lsdev.pl">SRP</a> w przeciwie&#x144;stwie do omawianej wcze&#x15B;niej klasy <em>Date</em>. Warto r&#xF3;wnie&#x17C; zwr&#xF3;ci&#x107; uwag&#x119; na to, &#x17C;e ka&#x17C;da z klas jest <em>immutable &amp; thread safe</em> co z pewno&#x15B;ci&#x105; wp&#x142;ywa na bezpiecze&#x144;stwo i jako&#x15B;&#x107; oprogramowania.</p><p>Zanim przejd&#x119; do om&#xF3;wienia najwa&#x17C;niejszych klas bardziej szczeg&#xF3;&#x142;owo warto zerkn&#x105;&#x107; na obrazek poni&#x17C;ej, kt&#xF3;ry jest pomocny w odnalezieniu si&#x119; w tym g&#x105;szczu klas.</p><figure class="kg-card kg-image-card"><img src="https://lsdev.pl/content/images/2022/11/javatime.webp" class="kg-image" alt="Przetwarzanie daty i czasu w j&#x119;zyku Java i pu&#x142;apki z tym zwi&#x105;zane" loading="lazy" width="1183" height="689" srcset="https://lsdev.pl/content/images/size/w600/2022/11/javatime.webp 600w, https://lsdev.pl/content/images/size/w1000/2022/11/javatime.webp 1000w, https://lsdev.pl/content/images/2022/11/javatime.webp 1183w" sizes="(min-width: 720px) 720px"></figure><h2 id="instant">Instant</h2><p>Reprezentuje okre&#x15B;lony moment w czasie. Pocz&#x105;tek zosta&#x142; wyznaczony na p&#xF3;&#x142;noc 1 stycznia 1970 roku. Pocz&#x105;wszy od tego punktu czas jest mierzony w sekundach po 86400 sekund na dob&#x119; w ty&#x142; oraz w do przodu z dok&#x142;adno&#x15B;ci&#x105; do nanosekund. Warto&#x15B;ci klasy <em>Instant</em> mog&#x105; okre&#x15B;la&#x107; czas si&#x119;gaj&#x105;cy miliarda lat wstecz (<em>Instant.MIN</em>). <em>Instant.MAX</em> odpowiada dacie 31 grudnia 1 000 000 000, je&#x17C;eli przekroczymy t&#x119; warto&#x15B;&#x107; otrzymamy wyj&#x105;tek <em>DateTimeException</em>.</p><!--kg-card-begin: html--><pre><code class="language-java line-numbers">@Test
void testInstant() {
	Instant now = Instant.now();
	Instant later = now.plusSeconds(3600);
	System.out.println(now + &quot;, &quot; + later); // 2022-11-05T17:56:40.669420Z, 2022-11-05T18:56:40.669420Z
	assertTrue(later.isAfter(now));
}
</code></pre><!--kg-card-end: html--><p><em>Instant</em> idealnie nadaje si&#x119; do rejestrowania czasu r&#xF3;&#x17C;nego rodzaju zdarze&#x144; lub log&#xF3;w.</p><h2 id="duration">Duration</h2><p>Reprezentuje odcinek czas pomi&#x119;dzy dwoma punktami w sekundach z dok&#x142;adno&#x15B;ci&#x105; do nanosekund gdzie 24h = 86400 sekund. D&#x142;ugo&#x15B;&#x107; <em>Duration</em> mo&#x17C;emy wygodnie pobiera&#x107; u&#x17C;ywaj&#x105;c takich metod jak <em>toNanos</em>, <em>toMillis</em>, <em>toSeconds</em>, <em>toMinutes</em>, <em>toHours</em>, <em>toDays</em>. Przydaje si&#x119; w wyliczaniu r&#xF3;&#x17C;nego rodzaju odleg&#x142;o&#x15B;ci w czasie.</p><!--kg-card-begin: html--><pre><code class="language-java line-numbers">@Test
void testDuration() {
	Instant now = Instant.now();
	Instant later = now.plus(4, ChronoUnit.HOURS);
	var duration = Duration.between(now, later);
	assertEquals(4, duration.toHours());
}
</code></pre><!--kg-card-end: html--><p>U&#x17C;ywaj&#x105;c <em>Duration</em> mo&#x17C;na r&#xF3;wnie&#x17C; w &#x142;atwy spos&#xF3;b &#x201E;skaka&#x107;&#x201D; <br>w czasie definiuj&#x105;c odpowiedni&#x105; d&#x142;ugo&#x15B;&#x107; w czasie.</p><!--kg-card-begin: html--><pre><code class="language-java line-numbers">Instant now = Instant.now();
Instant tomorrow = now.plus(Duration.ofDays(1));
</code></pre><!--kg-card-end: html--><p>Warto wiedzie&#x107;, &#x17C;e klasa Duration obs&#x142;uguje parsowanie w standardzie ISO-8601 w zwi&#x105;zku z tym mo&#x17C;na przekaza&#x107; do metody odpowiedni &#x142;a&#x144;cuch znak&#xF3;w. Dzi&#x119;ki temu mo&#x17C;emy w &#x142;atwy spos&#xF3;b wyrazi&#x107; odleg&#x142;o&#x15B;&#x107; w czasie za pomoc&#x105; jednej zmiennej, kt&#xF3;ra mo&#x17C;e by&#x107; wyniesiona do jakiej&#x15B; zewn&#x119;trznej konfiguracji.</p><!--kg-card-begin: html--><pre><code class="language-java">Duration duration = Duration.parse(&quot;P2DT3H4M&quot;); // 2 days, 3 hours and 4 minutes
</code></pre><!--kg-card-end: html--><h2 id="localdatetime">LocalDateTime</h2><p>Zawiera informacj&#x119; o dacie oraz czasie z nanosekundow&#x105; precyzj&#x105; natomiast nie udost&#x119;pnia &#x17C;adnych informacji na temat strefy czasowej. Z tego wzgl&#x119;du czas wyra&#x17C;ony z u&#x17C;yciem <em>LocalDateTime</em> nie mo&#x17C;e by&#x107; rozpatrywany jako bezwgl&#x119;dny poniewa&#x17C; mo&#x17C;e by&#x107; inny w zale&#x17C;no&#x15B;ci od tego gdzie si&#x119; znajdujemy w przeciwie&#x144;stwie do <em>Instant</em>, kt&#xF3;ry jest wyliczany bez wzgl&#x119;du na stref&#x119; czasow&#x105;.</p><!--kg-card-begin: html--><pre><code class="language-java">LocalDateTime now = LocalDateTime.now();
</code></pre><!--kg-card-end: html--><p>Domy&#x15B;lnie lokalna data jest wyliczana na podstawie domy&#x15B;lnej strefy czasowej ustawionej w systemie lub na maszynie wirtualnej. Warto pami&#x119;ta&#x107;, &#x17C;e w przypadku <em>LocalDateTime</em> nie nale&#x17C;y polega&#x107; na systemowej strefie czasowej<br>gdy&#x17C; wyniki mog&#x105; by&#x107; r&#xF3;&#x17C;ne w zale&#x17C;no&#x15B;ci od tego gdzie zostanie zainstalowana nasza aplikacja. Na szcz&#x119;&#x15B;cie mamy nad tym kontrol&#x119; i mo&#x17C;emy do metody statycznej przekaza&#x107; stref&#x119; czasow&#x105;, na podstawie kt&#xF3;rej zostanie wygenerowana data.</p><!--kg-card-begin: html--><pre><code class="language-java">LocalDateTime now = LocalDateTime.now(ZoneId.of(&quot;Europe/Warsaw&quot;));
</code></pre><!--kg-card-end: html--><p>Je&#x17C;eli chcemy globalnie ustawi&#x107; lokaln&#x105; stref&#x119; czasow&#x105; dla aplikacji mo&#x17C;emy przekaza&#x107; odpowiedni&#x105; warto&#x15B;&#x107; do maszyny wirtualnej w nast&#x119;puj&#x105;cy spos&#xF3;b.</p><!--kg-card-begin: html--><pre><code class="language-java">java -Duser.timezone=&quot;Europe/Warsaw&quot; -jar app.jar
</code></pre><!--kg-card-end: html--><h2 id="localtime">LocalTime</h2><p>Zawiera informacj&#x119; o lokalnym czasie z pomini&#x119;ciem strefy czasowej z nanosekundow&#x105; precyzj&#x105; i nie zwraca uwagi na podzia&#x142; AM/PM, tym zajmuj&#x105; si&#x119; mechanizmy odpowiedzialne za formatowanie.</p><p>Utworzenie obiektu tej klasy sprowadza si&#x119; do pobrania aktualnego czasu poprzez <em>LocalTime.now()</em> lub z g&#xF3;ry ustalonego czasu <em>LocalTime.of(hour, minute, second, nanoOfSecond) </em>gdzie:</p><ul><li><em>hour </em>- liczba z zakresu 0-23</li><li><em>minute</em> - liczba z zakresu 0-59</li><li><em>second</em> - liczba z zakresu 0-59</li><li><em>nanoOfSecond</em> - liczba z zakresu 0-999 999 999</li></ul><p></p><!--kg-card-begin: html--><pre><code class="language-java line-numbers">@Test
void localTimeTest() {
	LocalTime now = LocalTime.now(ZoneId.of(&quot;Europe/Warsaw&quot;));
	LocalTime later = now.plusHours(4);
	assertTrue(later.isAfter(now));
	assertEquals(4, Duration.between(now, later).toHours());	
}
</code></pre><!--kg-card-end: html--><h2 id="localdate">LocalDate</h2><p>Reprezentuje rok, miesi&#x105;c i dzie&#x144; bez informacji o czasie i strefie czasowej. Idealnie nadaje si&#x119; np. do zapisania daty urodzin lub innych wydarze&#x144; gdzie czas nie ma znaczenia.</p><!--kg-card-begin: html--><pre><code class="language-java">LocalDate birthday = LocalDate.of(2022, 11, 14);
</code></pre><!--kg-card-end: html--><p>Aby obliczy&#x107; dat&#x119; urodzin w przysz&#x142;ym roku wystarczy u&#x17C;y&#x107; wyra&#x17C;enia w postaci <em>birthday.plus(Period.ofYears(1))</em> lub po prostu <em>birthday.plusYears(1)</em>. Nale&#x17C;y pami&#x119;ta&#x107;, &#x17C;e w roku przest&#x119;pnym wywo&#x142;anie w postaci <em>birthday.plus(Period.ofDays(365))</em> zwr&#xF3;ci nieprawid&#x142;owy wynik.</p><p>Warto r&#xF3;wnie&#x17C; wspomnie&#x107;, &#x17C;e stworzenie daty 29 lutego w roku nieprzest&#x119;pnym np. <em>LocalDate.of(2023, 2, 29)</em> objawi si&#x119; wyj&#x105;tkiem typu DateTimeException.</p><h3 id="jak-wyliczy%C4%87-dzie%C5%84-programisty">Jak wyliczy&#x107; dzie&#x144; programisty</h3><p>Dzie&#x144; programisty wypada na 256 dzie&#x144; w roku. Jak go wyliczy&#x107; bior&#x105;c pod uwag&#x119;, &#x17C;e w roku przest&#x119;pnym jest o jeden dzie&#x144; wi&#x119;cej? Mo&#x17C;emy to zrobi&#x107; w nast&#x119;puj&#x105;cy spos&#xF3;b.</p><!--kg-card-begin: html--><pre><code class="language-java line-numbers">LocalDate programmersDayInCurrentYear = 
				LocalDate.now()
				.withMonth(1)
				.withDayOfMonth(1)
				.plusDays(255);
</code></pre><!--kg-card-end: html--><h2 id="zoneddatetime">ZonedDateTime</h2><p>ZonedDateTime reprezentuje dat&#x119; i czas wraz z stref&#x105; czasow&#x105; z nanosekundow&#x105; precyzj&#x105;. Baz&#x119; danych wszystkich stref czasowych na ca&#x142;ym &#x15B;wiecie przechowuje agencja <a href="http://www.iana.org/time-zones?ref=lsdev.pl">IANA</a> i aktualizuje j&#x105; kilka razy w roku. Java korzysta z tej bazy danych.</p><p>Ka&#x17C;da strefa czasowa ma sw&#xF3;j identyfikator np. <em>America/New_York</em> czy <em>Europe/Berlin </em>i aktualizuje j&#x105; kilka razy w roku. List&#x119; wszystkich dost&#x119;pnych stref czasowych mo&#x17C;emy pobra&#x107; przy u&#x17C;yciu <em>ZoneId.getAvailableZoneIds()</em>.</p><p>Dysponuj&#x105;c tym identyfikatorem mo&#x17C;na przekszta&#x142;ci&#x107; obiekt <em>LocalDateTime</em> na <em>ZonedDateTime.</em></p><!--kg-card-begin: html--><pre><code class="language-java line-numbers">LocalDateTime now = LocalDateTime.now();
ZonedDateTime znow = now.atZone(ZoneId.of(&quot;Europe/Warsaw&quot;));
System.out.println(now); // 2022-11-13T02:38:03.934777
System.out.println(znow); // 2022-11-13T02:38:03.934777+01:00[Europe/Warsaw]
</code></pre><!--kg-card-end: html--><p>Mo&#x17C;emy r&#xF3;wnie&#x17C; pobra&#x107; aktualny czas na podstawie domy&#x15B;lnej strefy czasowej tj. <em>ZonedDateTime.now()</em> lub bezpo&#x15B;rednio przekaza&#x107; identyfikator strefy <em>ZonedDateTime.now(ZoneId.of(&quot;Europe/Warsaw&quot;)).</em></p><p>Wywo&#x142;anie <em>LocalDateTime.atZone(...)</em> dodaj&#x119; stref&#x119; czasow&#x105; tworz&#x105;c obiekt typu <em>ZonedDateTime</em> jednak nie zmienia czasu na ten wskazany przez stref&#x119; czasow&#x105; (<em>LocalDateTime</em> nie ma informacji o strefie czasowej wi&#x119;c nawet nie by&#x142;oby to mo&#x17C;liwe). Je&#x17C;eli naszym celem jest r&#xF3;wnie&#x17C; zmiana czasu mo&#x17C;emy zrobi&#x107; to w nast&#x119;puj&#x105;cy spos&#xF3;b.</p><!--kg-card-begin: html--><pre><code class="language-java line-numbers">LocalDateTime someDate = LocalDateTime.parse(&quot;2022-11-10T18:40:00.000&quot;); // time in Europe/Warsaw zone
ZonedDateTime convertedToSpecificZone = ZonedDateTime.ofInstant(
someDate.atZone(ZoneId.of(&quot;Europe/Warsaw&quot;)).toInstant(),
ZoneId.of(&quot;America/New_York&quot;));
</code></pre><!--kg-card-end: html--><p>W przypadku konwersji <em>ZonedDateTime</em> -&gt; <em>ZonedDateTime</em> robimy podobnie.</p><!--kg-card-begin: html--><pre><code class="language-java line-numbers">ZonedDateTime someDateWithZone = ZonedDateTime.now(ZoneId.of(&quot;Europe/Warsaw&quot;));
ZonedDateTime newYorkTime = ZonedDateTime.ofInstant(someDateWithZone.toInstant(), ZoneId.of(&quot;America/New_York&quot;));
</code></pre><!--kg-card-end: html--><p>lub zwi&#x119;&#x17A;lej</p><!--kg-card-begin: html--><pre><code class="language-java">ZonedDateTime newYorkTime = someDateWithZone.withZoneSameInstant(ZoneId.of(&quot;America/New_York&quot;))
</code></pre><!--kg-card-end: html--><h2 id="offsetdatetime">OffsetDateTime</h2><p>Reprezentuj&#x119; dat&#x119; i czas wraz z przesuni&#x119;ciem od czasu UTC/Greenwich z nanosekundow&#x105; precyzj&#x105;. Upraszczaj&#x105;c <em>OffsetDateTime</em> jest to <em>Instant</em> + <em>ZoneOffset</em> i w taki te&#x17C; spos&#xF3;b mo&#x17C;emy utworzy&#x107; obiekt tej klasy.</p><!--kg-card-begin: html--><pre><code class="language-java line-numbers">OffsetDateTime offsetDateTime = Instant.now().atOffset(ZoneOffset.of(&quot;+01:00&quot;));
// lub
OffsetDateTime now = OffsetDateTime.now();
</code></pre><!--kg-card-end: html--><p>W przeciwie&#x144;stwie do klasy <em>ZonedDateTime</em> nie mamy tu informacji o strefie czasowej a tym samym o przybli&#x17C;onej lokalizacji geograficznej, jedynie sztywno zdefiniowany <em>offset</em>. Warto o tym pami&#x119;ta&#x107; szczeg&#xF3;lnie gdy nast&#x119;puje zmiana czasu, r&#xF3;&#x17C;nic&#x119; mo&#x17C;emy zobaczy&#x107; w kodzie poni&#x17C;ej, gdzie w dzie&#x144; zmiany czasu z letniego na zimowy przesun&#x119;li&#x15B;my si&#x119; w czasie o 4 godziny.</p><!--kg-card-begin: html--><pre><code class="language-java line-numbers">LocalDateTime dateTime = LocalDateTime.parse(&quot;2022-10-30T00:00:00.000&quot;);
ZonedDateTime zonedDateTime = dateTime.atZone(ZoneId.of(&quot;Europe/Warsaw&quot;)).plusHours(4);
OffsetDateTime offsetDateTime = dateTime.atOffset(ZoneOffset.of(&quot;+02:00&quot;)).plusHours(4);
System.out.println(zonedDateTime); // 2022-10-30T03:00+01:00[Europe/Warsaw]
System.out.println(offsetDateTime); // 2022-10-30T04:00+02:00
</code></pre><!--kg-card-end: html--><h2 id="period">Period</h2><p>Klasa mierz&#x105;ca odcinek czasu jako ilo&#x15B;&#x107; lat, miesi&#x119;cy i dni w odr&#xF3;&#x17C;nieniu od klasy <em>Duration</em> om&#xF3;wionej wcze&#x15B;niej, gdzie najmniejsz&#x105; jednostk&#x105;, kt&#xF3;ra mierzy up&#x142;yw czasu by&#x142;y sekundy/nanosekundy.</p><p>Z tego wzgl&#x119;du Period u&#x17C;yjemy raczej do wyliczenia r&#xF3;&#x17C;nicy mi&#x119;dzy dwoma datami typu <em>LocalDate.</em></p><!--kg-card-begin: html--><pre><code class="language-java line-numbers">var now = LocalDate.parse(&quot;2022-11-10&quot;);
var later = now.plusDays(64);
var period = Period.between(now, later);
System.out.println(period.getMonths()); // 2
System.out.println(period.getDays()); // 3
</code></pre><!--kg-card-end: html--><p>Warto zwr&#xF3;ci&#x107; uwag&#x119;, &#x17C;e mimo i&#x17C; powy&#x17C;ej dodali&#x15B;my 64 dni metoda <em>getDays()</em> zwraca nam 3 gdy&#x17C; reszta dni zosta&#x142;a skonsolidowana do ilo&#x15B;ci miesi&#x119;cy.</p><p><em>Period</em> r&#xF3;&#x17C;ni si&#x119; tak&#x17C;e od <em>Duration</em> sposobem traktowania czasu letniego/zimowego w przypadku gdy dodajemy czas do <em>ZonedDateTime</em>. Jak wspomnieli&#x15B;my wcze&#x15B;niej <em>Duration</em> traktuje dzie&#x144; zawsze jako 24h czyli 86400 sekund wi&#x119;c dok&#x142;adnie o tyle przesuniemy czas w ty&#x142; lub w prz&#xF3;d, w przeciwie&#x144;stwie do <em>Period</em> gdzie koncepcja dnia stara si&#x119; zachowa&#x107; czas lokalny. Naj&#x142;atwiej b&#x119;dzie zobaczy&#x107; to na przyk&#x142;adzie.</p><!--kg-card-begin: html--><pre><code class="language-java line-numbers">var dateTime = ZonedDateTime.parse(&quot;2022-10-29T09:00+02:00[Europe/Warsaw]&quot;);
System.out.println(dateTime.plus(Duration.ofDays(7))); // 2022-11-05T08:00+01:00[Europe/Warsaw]
System.out.println(dateTime.plus(Period.ofDays(7))); // 2022-11-05T09:00+01:00[Europe/Warsaw]
</code></pre><!--kg-card-end: html--><p>Ustawi&#x142;em dat&#x119; na dzie&#x144; przed zmian&#x105; czasu z letniego na zimowy. Dodaj&#x105;c r&#xF3;wno 7 dni do tej daty u&#x17C;ywaj&#x105;c <em>Duration</em> godzina r&#xF3;wnie&#x17C; si&#x119; zmieni&#x142;a z 9 na 8. Inaczej jest w przypadku <em>Period</em> gdzie godzina zosta&#x142;a zachowana (w przypadku <em>LocalDateTime</em> w obu przypadkach by&#x142;aby taka sama godzina tj. 9 gdy&#x17C; ta klasa nie bierze pod uwag&#x119; zmiany czasu). Warto o tym pami&#x119;ta&#x107; gdy np. chcemy zaplanowa&#x107; jakie&#x15B; spotkanie w przysz&#x142;o&#x15B;ci.</p><h2 id="timeadjusters">TimeAdjusters</h2><p>Cz&#x119;sto mamy potrzeb&#x119; wyliczenia pierwszego/ostatniego dnia miesi&#x105;ca/roku/tygodnia lub innych tego typu wylicze&#x144;. Z pomoc&#x105; przychodzi klasa <a href="https://docs.oracle.com/javase/8/docs/api/java/time/temporal/TemporalAdjusters.html?ref=lsdev.pl"><em>TemporalAdjusters</em></a><em>. </em>Przyk&#x142;adowo wyliczenie pierwszego dnia miesi&#x105;ca mo&#x17C;e wygl&#x105;da&#x107; nast&#x119;puj&#x105;co<em>.</em></p><!--kg-card-begin: html--><pre><code class="language-java line-numbers">LocalDate firstDayOfMonth = now.with(TemporalAdjusters.firstDayOfMonth());
// lub
LocalDate firstDayOfMonth =  (LocalDate) TemporalAdjusters.firstDayOfMonth().adjustInto(now);
</code></pre><!--kg-card-end: html--><p>Dost&#x119;pnych jest wiele innych modyfikator&#xF3;w m.in.</p><!--kg-card-begin: html--><pre><code class="language-java line-numbers">TemporalAdjusters.firstDayOfMonth();
TemporalAdjusters.lastDayOfMonth();
TemporalAdjusters.firstDayOfYear();
TemporalAdjusters.firstDayOfNextMonth();
TemporalAdjusters.firstDayOfNextYear();
TemporalAdjusters.firstInMonth(DayOfWeek.SATURDAY);
TemporalAdjusters.previous(DayOfWeek.FRIDAY);
</code></pre><!--kg-card-end: html--><h2 id="formatowanie-parsowanie-i-wy%C5%9Bwietlanie">Formatowanie, parsowanie i wy&#x15B;wietlanie</h2><p>Aplikacje nad kt&#xF3;rymi pracujemy, cz&#x119;sto wymagaj&#x105; wy&#x15B;wietlenia a tym samym formatowania i parsowania daty i czasu na r&#xF3;&#x17C;ne sposoby - czasami celem jest serializacja i przes&#x142;anie warto&#x15B;ci przez sie&#x107; a czasem po prostu zwyk&#x142;e wy&#x15B;wietlenie.</p><p>W najprostszym przypadku mo&#x17C;emy skorzysta&#x107; z domy&#x15B;lnego formatu tj. zgodnego z standardem ISO-8601 wywo&#x142;uj&#x105;c metod&#x119; <em>toString()</em> na obiekcie daty.</p><!--kg-card-begin: html--><pre><code class="language-java line-numbers">String now = LocalDate.now().toString();
System.out.println(now); // 2022-11-11
LocalDate localDate = LocalDate.parse(now);
</code></pre><!--kg-card-end: html--><p>Jednak najcz&#x119;&#x15B;ciej to nam nie wystarczy. Chcieliby&#x15B;my mie&#x107; wi&#x119;ksz&#x105; kontrol&#x119; nad formatowaniem i parsowaniem czasu. Z pomoc&#x105; przychodzi klasa <em>DateTimeFormatter</em>, kt&#xF3;ra pozwala bardzo precyzyjnie to wyrazi&#x107;.</p><p>Zdefiniowanie formatu sprowadza si&#x119; do stworzenia <br>obiektu tej klasy na podstawie odpowiedniego wzorca.</p><!--kg-card-begin: html--><pre><code class="language-java line-numbers">LocalDate localDate = LocalDate.parse(&quot;2022-10-14&quot;);
var formatter = DateTimeFormatter.ofPattern(&quot;dd LLLL, yyyy&quot;);
String formatted = localDate.format(formatter); // lub formatter.format(localDate);
System.out.println(formatted); // 14 October, 2022
</code></pre><!--kg-card-end: html--><p>W celu wy&#x15B;wietlenia daty w rodzimym j&#x119;zyku podczas <br>tworzenia <em>formattera</em> nale&#x17C;y przekaza&#x107; odpowiedni <em>Locale</em>.</p><!--kg-card-begin: html--><pre><code class="language-java line-numbers">LocalDate localDate = LocalDate.parse(&quot;2022-10-14&quot;);
Locale pl = new Locale(&quot;pl&quot;, &quot;PL&quot;);
var formatter = DateTimeFormatter.ofPattern(&quot;dd LLLL, yyyy&quot;, pl);
System.out.println(localDate.format(formatter)); // 14 pa&#x17A;dziernik, 2022
</code></pre><!--kg-card-end: html--><p>Istnieje r&#xF3;wnie&#x17C; wiele predefiniowanych format&#xF3;w zgodnych <br>z standardem ISO-8601, kt&#xF3;re mo&#x17C;emy wykorzysta&#x107; m.in.</p><!--kg-card-begin: html--><pre><code class="language-java line-numbers">DateTimeFormatter.ISO_LOCAL_DATE
DateTimeFormatter.ISO_LOCAL_DATE_TIME
DateTimeFormatter.ISO_INSTANT
...
</code></pre><!--kg-card-end: html--><h2 id="testowanie">Testowanie</h2><p>Aby napisane przez nas oprogramowanie by&#x142;o solidne warto zadba&#x107; o dobre testy jednostkowe i integracyjne. </p><p>W przypadku gdy testowany przez nas kod pobiera aktualny czas (np. <em>Instant.now()</em>) i na jego podstawie wykonuje pewn&#x105; logik&#x119; nasz kod staje si&#x119; trudno testowalny, gdy&#x17C; nie mamy kontroli nad up&#x142;ywaj&#x105;cym czasem, kt&#xF3;ry za ka&#x17C;dym uruchomieniem testu b&#x119;dzie inny.</p><p>Z pomoc&#x105; przychodzi klasa <a href="https://docs.oracle.com/javase/8/docs/api/java/time/Clock.html?ref=lsdev.pl"><em>Clock</em></a> i jej pochodne, dzi&#x119;ki kt&#xF3;rym mo&#x17C;emy w prosty spos&#xF3;b skonfigurowa&#x107; &#x17A;r&#xF3;d&#x142;o, na podstawie kt&#xF3;rego czas b&#x119;dzie pobierany.</p><!--kg-card-begin: html--><pre><code class="language-java line-numbers">Clock clock = Clock.system(ZoneId.of(&quot;Europe/Warsaw&quot;));
Instant instant = Instant.now(clock);
</code></pre><!--kg-card-end: html--><p>Je&#x17C;eli korzystamy z mechanizmu wstrzykiwania zale&#x17C;no&#x15B;ci np. w Spring&apos;u mo&#x17C;emy p&#xF3;j&#x15B;&#x107; o krok dalej i stworzy&#x107; komponent, kt&#xF3;ry b&#x119;dzie wstrzykiwany tam gdzie nast&#x105;pi potrzeba pobierania czasu systemowego.</p><!--kg-card-begin: html--><pre><code class="language-java line-numbers">@Configuration
class AppConfig {

	@Bean
	public Clock clock() {
		return Clock.system(ZoneId.of(&quot;Europe/Warsaw&quot;));
	}
}

@Service
class SomeService {

	private final Clock clock;

	public SomeService(Clock clock) {
		this.clock = clock;
	}

	public String bar() {
        return &quot;Hello world &quot; + Instant.now(clock);
    }
}
</code></pre><!--kg-card-end: html--><p>Nast&#x119;pnie mo&#x17C;emy przej&#x15B;&#x107; do przetestowania funkcjonalno&#x15B;ci podmieniaj&#x105;c implementacj&#x119; <em>Clock</em>&apos;a aby odzyska&#x107; kontrol&#x119; w testach.</p><!--kg-card-begin: html--><pre><code class="language-java line-numbers">@Test
void barTest() {
    // given
    Clock clock = Clock.fixed(Instant.parse(&quot;2022-11-12T10:39:33.307871Z&quot;), ZoneId.of(&quot;Europe/Warsaw&quot;));
    var subject = new SomeService(clock);

    // when
    String result = subject.bar();

    // then
    assertEquals(&quot;Hello world 2022-11-12T10:39:33.307871Z&quot;, result);
}
</code></pre><!--kg-card-end: html--><h2 id="pu%C5%82apki">Pu&#x142;apki</h2><h3 id="planowanie-spotka%C5%84">Planowanie spotka&#x144;</h3><p>Za&#x142;&#xF3;&#x17C;my, &#x17C;e chcemy zaplanowa&#x107; spotkanie co tydzie&#x144; o 14:30 czasu w strefie Europe/Warsaw. W tym celu musimy doda&#x107; do ostatniego czasu strefowego 7 dni czyli 7 * 4 * 3600 sekund, jednak je&#x15B;li przy tym przekroczymy granice zmiany czasu z letniego na zimowy oka&#x17C;e si&#x119;, &#x17C;e spotkanie wypadnie o godzin&#x119; za wcze&#x15B;nie lub za p&#xF3;&#x17A;no.</p><!--kg-card-begin: html--><pre><code class="language-java line-numbers">ZonedDateTime dateTime = ZonedDateTime.of(LocalDateTime.of(2022, 10, 29, 14, 30), ZoneId.of(&quot;Europe/Warsaw&quot;));
System.out.println(dateTime.plusSeconds(7 * 24 * 3600)); // 13:30 !!!
</code></pre><!--kg-card-end: html--><p>Z tego powodu projektanci API zalecaj&#x105; aby godzin z informacjami o strefie czasowej u&#x17C;ywa&#x107; jedynie wtedy gdy pos&#x142;ugiwanie si&#x119; bezwzgl&#x119;dnymi momentami czasu jest absolutnie niezb&#x119;dne. Urodziny, &#x15B;wi&#x119;ta, zaplanowane spotkania najlepiej zapisywa&#x107; przy u&#x17C;yciu lokalnych dat i godzin.</p><!--kg-card-begin: html--><pre><code class="language-java line-numbers">LocalDateTime localDateTime = LocalDateTime.of(2022, 10, 29, 14, 30);
System.out.println(localDateTime.plusSeconds(7 * 24 * 3600)); // 14:30 OK
</code></pre><!--kg-card-end: html--><h3 id="u%C5%BCywanie-localdatetime-gdy-musimy-mie%C4%87-wi%C4%99cej-informacji">U&#x17C;ywanie <em>LocalDateTime</em> gdy musimy mie&#x107; wi&#x119;cej informacji</h3><p>Jak wiemy klasa <em>LocalDateTime</em> nie posiada informacji o &#x17C;adnym <em>offsecie </em>od czasu UTC a tym samym o strefie czasowej. Je&#x17C;eli przyk&#x142;adowo tworzymy aplikacj&#x119;, kt&#xF3;ra ma za zadanie przypomina&#x107; u&#x17C;ytkownika o r&#xF3;&#x17C;nych wydarzeniach i dat&#x119; rozpocz&#x119;cia wydarzenia zapiszemy w postaci <em>LocalDateTime</em> tak naprawd&#x119; utracimy informacj&#x119; o faktycznym czasie rozpocz&#x119;cia (chyba, &#x17C;e zak&#x142;adamy, &#x17C;e u&#x17C;ywamy tylko jednej globalnej strefy czasowej). Z tego wzgl&#x119;du warto mie&#x107; w takim przypadku dodatkow&#x105; informacj&#x119; tj. stref&#x119; czasow&#x105; w jakiej operuje u&#x17C;ytkownik.</p><h3 id="b%C5%82%C4%99dy-zwi%C4%85zane-z-formatowaniem">B&#x142;&#x119;dy zwi&#x105;zane z formatowaniem</h3><ul><li>U&#x17C;ywanie &quot;mm&quot; dla miesi&#x119;cy i &quot;MM&quot; dla minut i odwrotnie</li><li>U&#x17C;ywanie &quot;DD&quot; jako dzie&#x144; miesi&#x105;ca zamiast roku, w rzeczywisto&#x15B;ci dzie&#x144; miesi&#x105;ca to &quot;dd&quot;</li><li>U&#x17C;ywanie &quot;hh&quot; (1-12) jako godzina w danym dniu zamiast &quot;HH&quot; (0-23)</li><li>U&#x17C;ywanie &quot;YYYY&quot; do formatowania roku zamiast &quot;yyyy&quot;</li></ul><h2 id="podsumowanie">Podsumowanie</h2><p>Java 8 przynios&#x142;a liczne i d&#x142;ugo wyczekiwane zmiany w kontek&#x15B;cie <em>date &amp; time</em> API. Nowe API jest bezpieczniejsze i prostsze w u&#x17C;yciu, z pewno&#x15B;ci&#x105; wi&#x119;c warto jest nim zast&#x105;pi&#x107; poprzednie implementacje. Na koniec kilka dobrych praktyk, kt&#xF3;re mog&#x105; Ci si&#x119; przyda&#x107; przy pracy z datami i czasem:</p><ul><li>Unikaj u&#x17C;ywania konstrukcji daty i czasu, kt&#xF3;re s&#x105; zbyt szczeg&#xF3;&#x142;owe lub zbyt og&#xF3;lne</li><li>Nie polegaj na systemowej strefie czasowej lub tej ustawionej w bazie danych</li><li>U&#x17C;ywaj <em>Clock</em> w celu konfiguracji &#x17A;r&#xF3;d&#x142;a, na podstawie kt&#xF3;rego czas b&#x119;dzie pobierany</li><li>Staraj si&#x119; u&#x17C;ywa&#x107; formatu w standardzie ISO-8601 w API, kt&#xF3;re oferujesz</li><li>U&#x17C;ywaj <em>ZonedDateTime</em> je&#x15B;li musisz uwzgl&#x119;dnia&#x107; strefy czasowe i zmiany czasu</li><li>U&#x17C;ywaj <em>Instant</em> je&#x15B;li chcesz zapisa&#x107; czas wyst&#x105;pienia jakiego&#x15B; zdarzenia jako punkt na osi czasu</li><li>Przechowuj czas w UTC i konwertuj na czas lokalny po stronie frontendu je&#x17C;eli musisz wy&#x15B;wietla&#x107; daty z uwzgl&#x119;dnieniem r&#xF3;&#x17C;nych stref czasowych</li><li>Unikaj u&#x17C;ywania legacy <em>date-time</em> API w Java na rzecz nowego API</li><li>Pami&#x119;taj o walidacji p&#xF3;l wpisywanych przez u&#x17C;ytkownika</li><li>Pami&#x119;taj o testach jednostkowych szczeg&#xF3;lnie przypadk&#xF3;w brzegowych tj.<br>- pocz&#x105;tek/koniec miesi&#x105;ca/roku<br>- dni powszednie i weekendy<br>- 29 lutego<br>- strefy czasowe, czas letni </li></ul><p>Po wi&#x119;cej szczeg&#xF3;&#x142;owych informacji zach&#x119;cam do przejrzenia <a href="https://docs.oracle.com/javase/8/docs/api/java/time/package-summary.html?ref=lsdev.pl">dokumentacji</a> a tak&#x17C;e do zerkni&#x119;cia na &#x15B;wietn&#x105; <a href="https://youtu.be/zsfEWLGgsEY?ref=lsdev.pl">prezentacj&#x119;</a> autorstwa Tomasza Nurkiewicza. A tymczasem zostawiam Ci&#x119; z utworem i do nast&#x119;pnego &#x1F44B;</p><!--kg-card-begin: html--><iframe width="100%" height="166" scrolling="no" frameborder="no" allow="autoplay" src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/646207815&amp;color=%23ff5500&amp;auto_play=false&amp;hide_related=false&amp;show_comments=true&amp;show_user=true&amp;show_reposts=false&amp;show_teaser=true"></iframe><div style="font-size: 10px; color: #cccccc;line-break: anywhere;word-break: normal;overflow: hidden;white-space: nowrap;text-overflow: ellipsis; font-family: Interstate,Lucida Grande,Lucida Sans Unicode,Lucida Sans,Garuda,Verdana,Tahoma,sans-serif;font-weight: 100;"><a href="https://soundcloud.com/manual-music?ref=lsdev.pl" title="Manual Music" target="_blank" style="color: #cccccc; text-decoration: none;">Manual Music</a> &#xB7; <a href="https://soundcloud.com/manual-music/stil-corners-the-trip-bruno-andrada-unofficial-remix?ref=lsdev.pl" title="FREE DOWNLOAD: Stil Corners - The Trip (Bruno Andrada Unofficial Remix)" target="_blank" style="color: #cccccc; text-decoration: none;">FREE DOWNLOAD: Stil Corners - The Trip (Bruno Andrada Unofficial Remix)</a></div><!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[Flutter, narzędzie do tworzenia natywnych aplikacji mobilnych]]></title><description><![CDATA[<p>W tym artykule chcia&#x142;bym przedstawi&#x107; wam najbardziej obiecuj&#x105;cy Framework do tworzenia multiplatformowych aplikacji. Rozpoczn&#x119; od nakre&#x15B;lenia czym w og&#xF3;le jest Flutter sk&#x105;d si&#x119; wzi&#x105;&#x142; i dlaczego w bardzo kr&#xF3;tkim czasie zyska&#x142; tak&</p>]]></description><link>https://lsdev.pl/posts/flutter-narzedzie-do-tworzenia-natywnych-aplikacji-mobilnych/</link><guid isPermaLink="false">629b544f0bf93455df595e16</guid><category><![CDATA[Flutter]]></category><category><![CDATA[Mobile]]></category><category><![CDATA[Programming]]></category><dc:creator><![CDATA[Damian]]></dc:creator><pubDate>Sat, 04 Jun 2022 23:39:27 GMT</pubDate><media:content url="https://lsdev.pl/content/images/2022/06/flutter-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://lsdev.pl/content/images/2022/06/flutter-1.png" alt="Flutter, narz&#x119;dzie do tworzenia natywnych aplikacji mobilnych"><p>W tym artykule chcia&#x142;bym przedstawi&#x107; wam najbardziej obiecuj&#x105;cy Framework do tworzenia multiplatformowych aplikacji. Rozpoczn&#x119; od nakre&#x15B;lenia czym w og&#xF3;le jest Flutter sk&#x105;d si&#x119; wzi&#x105;&#x142; i dlaczego w bardzo kr&#xF3;tkim czasie zyska&#x142; tak&#x105; popularno&#x15B;&#x107;.</p><p><a href="https://flutter.dev/?ref=lsdev.pl">Flutter</a> rozpocz&#x105;&#x142; swoje istnienie w 2015 roku kiedy to na konferencji Dart Developer Summit firma Google zaprezentowa&#x142;a nowy projekt, pocz&#x105;tkowo pod nazw&#x105; &#x201E;Sky&#x201D;. Na pierwsz&#x105; wersj&#x119; przysz&#x142;o nam nieco poczeka&#x107;, wersja alfa by&#x142;a dost&#x119;pna od maja 2017 roku. Nast&#x119;pnie up&#x142;yn&#x105;&#x142; nieco ponad rok i we wrze&#x15B;niu 2018 roku udost&#x119;pniono wersj&#x119; beta. Pierwsz&#x105; wersj&#x119; stabiln&#x105; otrzymali&#x15B;my 4 grudnia 2018 podczas Flutter Live Event. W chwili pisania tego artyku&#x142;u najnowsza dost&#x119;pna wersja ma numer 3.0.1.</p><h3 id="co-to-jest-flutter">Co to jest Flutter?</h3><p>Naj&#x142;atwiej jest go zdefiniowa&#x107; jako zestaw narz&#x119;dzi do tworzenia natywnych aplikacji webowych, desktopowych<strong> </strong>oraz wbudowanych. Tutaj warto podkre&#x15B;li&#x107;, &#x17C;e nie ma potrzeby rozdzielania kodu na poszczeg&#xF3;lne wersje, wystarczy jedna aby cieszy&#x107; si&#x119; mobiln&#x105; aplikacj&#x105; oraz to samo zobaczy&#x107; w przegl&#x105;darce internetowej. Flutter to framework typu open-source dzi&#x119;ki czemu mamy dost&#x119;p do ogromnej liczby <a href="https://pub.dev/?ref=lsdev.pl">pakiet&#xF3;w</a>, kt&#xF3;re bardzo cz&#x119;sto s&#x105; gotowym do u&#x17C;ycia rozwi&#x105;zaniem naszych problem&#xF3;w.</p><h3 id="cechy">Cechy</h3><p>Czym&#x17C;e by&#x142;by dobry Framework bez szeregu funkcjonalno&#x15B;ci/cech/atrybut&#xF3;w, kt&#xF3;re u&#x142;atwi&#x105; nam prac&#x119;. Do najwa&#x17C;niejszych zaliczy&#x142;bym:</p><ul><li>Multiplatformowo&#x15B;&#x107; - piszesz kod raz i odpalasz &#x201E;wsz&#x119;dzie&#x201D;</li><li>Szybki development - dzi&#x119;ki temu, &#x17C;e kod zapisujemy w obiektowym j&#x119;zyku Dart, kt&#xF3;ry wykorzystuje strategi&#x119; <a href="https://en.wikipedia.org/wiki/Just-in-time_compilation?ref=lsdev.pl">JIT</a></li><li>Widgety - zar&#xF3;wno te, kt&#xF3;re napiszemy jak i te dost&#x119;pne w bazie, to szybki spos&#xF3;b na rozwijanie aplikacji</li><li>Hot reload - zmiany widoczne od razu po zapisaniu fragmentu kodu, bez konieczno&#x15B;ci przebudowywania czy prze&#x142;adowywania aplikacji. S&#x105; od tego oczywi&#x15B;cie wyj&#x105;tki dla przyk&#x142;adu zmiany w serwisach mog&#x105; wymaga&#x107; prze&#x142;adowania (hot restart)</li><li>Dost&#x119;p do natywnych funkcji i SDK platformy - gwarantuje to szybkie tworzenie funkcjonalno&#x15B;ci przy u&#x17C;yciu wbudowanych funkcji Flutter&#x2019;a jak r&#xF3;wnie&#x17C; mamy mo&#x17C;liwo&#x15B;&#x107; si&#x119;gni&#x119;cia do SDK platformy, na kt&#xF3;r&#x105; aktualnie tworzymy aplikacj&#x119;. Pierwszym z brzegu przyk&#x142;adem mo&#x17C;e by&#x107; konfiguracja powiadomie&#x144; typu push dla Android i iOS wykorzystuj&#x105;c ich domy&#x15B;lne rozwi&#x105;zania</li><li>Funkcjonalno&#x15B;ci nie opieraj&#x105; si&#x119; na WebView - WebView mo&#x17C;na w skr&#xF3;cie zdefiniowa&#x107; jako zwyk&#x142;&#x105; stron&#x119; Web pozbawion&#x105; interfejsu przegl&#x105;darki. Obecnie istotne jest aby unika&#x107; tego podej&#x15B;cia przy tworzeniu rozwi&#x105;za&#x144; mobilnych poniewa&#x17C; firmy takie jak Apple blokuj&#x105; aplikacje oparte na WebView w swoich sklepach. Flutter tworzy kod natywny, kt&#xF3;ry jest przez jego silnik obs&#x142;ugiwany, dzi&#x119;ki czemu aplikacje wy&#x15B;wietlaj&#x105; si&#x119; p&#x142;ynnie w 60 klatkach na sekund&#x119;.</li></ul><h3 id="wszystko-jest-widgetem">Wszystko jest Widgetem</h3><figure class="kg-card kg-image-card"><img src="https://lsdev.pl/content/images/2022/06/Flutter_diagram-widgetclass.webp" class="kg-image" alt="Flutter, narz&#x119;dzie do tworzenia natywnych aplikacji mobilnych" loading="lazy" width="1286" height="718" srcset="https://lsdev.pl/content/images/size/w600/2022/06/Flutter_diagram-widgetclass.webp 600w, https://lsdev.pl/content/images/size/w1000/2022/06/Flutter_diagram-widgetclass.webp 1000w, https://lsdev.pl/content/images/2022/06/Flutter_diagram-widgetclass.webp 1286w" sizes="(min-width: 720px) 720px"></figure><p>Wszystko co widzimy w naszej aplikacji napisanej w frameworku Flutter jest okre&#x15B;lane jako <em>widget</em>. Aplikacje tworzymy z gotowych widget&#xF3;w, co mo&#x17C;na por&#xF3;wna&#x107; do uk&#x142;adania domku z klock&#xF3;w. Struktur&#x119; dziedziczenia widget&#xF3;w przedstawia powy&#x17C;szy wykres.</p><p><strong>Stateful</strong></p><ul><li>dynamiczny <em>widget</em> posiadaj&#x105;cy swoje w&#x142;a&#x15B;ciwo&#x15B;ci znane pod nazw&#x105; <strong>state</strong></li><li><em>setState()</em> jest metod&#x105; u&#x17C;ywan&#x105; do aktualizacji stanu</li><li><em>widget</em> mo&#x17C;e by&#x107; przerysowany wielokrotnie podczas dzia&#x142;ania aplikacji</li><li><em>widgety</em> s&#x105; mutowalne, u&#x17C;ywamy ich gdy tre&#x15B;&#x107; zmienia si&#x119; dynamicznie</li><li>z <em>widgetem</em> stanowym zwi&#x105;zany jest cykl &#x17C;ycia, kt&#xF3;ry zaczyna si&#x119; od metody <em>createState</em> a ko&#x144;czy na <em>dispose</em></li></ul><p><strong>Stateless</strong></p><ul><li>niemutowalne widgety nieposiadaj&#x105;ce stanu i nie zmieniaj&#x105;ce swojego stanu podczas dzia&#x142;ania aplikacji</li><li>metoda <em>build()</em> wywo&#x142;ywana jest tylko raz</li><li>przyk&#x142;adem jest widget <em>Icon</em>, <em>IconButton </em>czy <em>Text</em></li></ul><p>Jestem przekonany, &#x17C;e w swoich aplikacjach znajdziecie najwi&#x119;ksz&#x105; liczb&#x119; widget&#xF3;w o nazwie <em>Container</em>, zaraz po nim b&#x119;d&#x105; plasowa&#x142;y si&#x119; takie podstawowe struktury jak <em>Text</em>, <em>Row</em>, <em>Column</em>, <em>Image</em>, <em>Icon</em>. Wszystko to zacznie si&#x119; od skromnego <em>Scaffold</em>, kt&#xF3;ry jest szkieletem, daj&#x105;cym mo&#x17C;liwo&#x15B;&#x107; skorzystania z takiej struktury jak <em>appBary</em>, <em>bottomBary</em> czy <em>backButtony</em>.</p><h3 id="podzia%C5%82-widget%C3%B3w">Podzia&#x142; widget&#xF3;w</h3><figure class="kg-card kg-image-card"><img src="https://lsdev.pl/content/images/2022/06/image.png" class="kg-image" alt="Flutter, narz&#x119;dzie do tworzenia natywnych aplikacji mobilnych" loading="lazy" width="949" height="323" srcset="https://lsdev.pl/content/images/size/w600/2022/06/image.png 600w, https://lsdev.pl/content/images/2022/06/image.png 949w" sizes="(min-width: 720px) 720px"></figure><p>Zdecydowana wi&#x119;kszo&#x15B;&#x107; widget&#xF3;w nale&#x17C;y do grupy generycznych, tych kt&#xF3;re udost&#x119;pnia nam Framework. Oznacza to, &#x17C;e niezale&#x17C;nie od platformy b&#x119;d&#x105; wygl&#x105;da&#x142;y i zachowywa&#x142;y si&#x119; tak samo. Znajd&#x105; si&#x119; jednak te&#x17C; takie, kt&#xF3;re b&#x119;d&#x105; dost&#x119;pne tylko dla danej platformy jak r&#xF3;wnie&#x17C; w ca&#x142;ym tym bogactwie znajdziemy te, kt&#xF3;re w zale&#x17C;no&#x15B;ci od platformy b&#x119;d&#x105; prezentowa&#x142;y si&#x119; inaczej. Przyk&#x142;adem jest widget wyboru daty i czasu.</p><h3 id="podobie%C5%84stwa-do-html">Podobie&#x144;stwa do HTML</h3><p>Zastanawiasz si&#x119; zapewne czy stworzenie w&#x142;asnego widgetu jest trudne. Ot&#xF3;&#x17C; dla os&#xF3;b zaznajomionych ze sk&#x142;adni&#x105; j&#x119;zyka HTML nie powinno to stanowi&#x107; wi&#x119;kszego problemu. Poni&#x17C;ej przedstawiam fragment kodu por&#xF3;wnuj&#x105;cy sk&#x142;adni&#x119; HTML z sk&#x142;adni&#x105; Flutter&#x2019;a.</p><figure class="kg-card kg-image-card"><img src="https://lsdev.pl/content/images/2022/06/compare.webp" class="kg-image" alt="Flutter, narz&#x119;dzie do tworzenia natywnych aplikacji mobilnych" loading="lazy" width="750" height="345" srcset="https://lsdev.pl/content/images/size/w600/2022/06/compare.webp 600w, https://lsdev.pl/content/images/2022/06/compare.webp 750w" sizes="(min-width: 720px) 720px"></figure><p>Oczywi&#x15B;cie podobnych lub takich samych tag&#xF3;w jest znacznie wi&#x119;cej. Znajome bez w&#x105;tpienia b&#x119;d&#x105; <em>Row</em>, <em>Column</em>, <em>ScrollView</em> i wiele innych.</p><h3 id="przydatne-pakiety">Przydatne pakiety</h3><ul><li><a href="https://pub.dev/packages/get?ref=lsdev.pl">get</a> - pakiet, kt&#xF3;ry &#x142;&#x105;czy w sobie kilka funkcjonalno&#x15B;ci, zaczynaj&#x105;c od najwa&#x17C;niejszej - menad&#x17C;era stanu przez wstrzykiwanie zale&#x17C;no&#x15B;ci po kontrol&#x119; przej&#x15B;&#x107; mi&#x119;dzy stronami aplikacji. W ogromie funkcjonalno&#x15B;ci tego dodatku warto wspomnie&#x107; o takich mo&#x17C;liwo&#x15B;ciach jak internacjonalizacja czy zmiana motywu aplikacji</li><li><a href="https://pub.dev/packages/file_picker?ref=lsdev.pl">file_picker</a> - pakiet, kt&#xF3;ry pozwoli na korzystanie z natywnego eksploratora plik&#xF3;w wraz z mo&#x17C;liwo&#x15B;ci&#x105; filtrowania. Pakiet pozwala tak&#x17C;e na wybieranie plik&#xF3;w bezpo&#x15B;rednio z chmury</li><li><a href="https://pub.dev/packages/rate_my_app?ref=lsdev.pl">rate_my_app</a> - gdy chcemy umo&#x17C;liwi&#x107; u&#x17C;ytkownikom aplikacji jej ocen&#x119; warto si&#x119;gn&#x105;&#x107; po rate my app, oczywi&#x15B;cie najlepiej w chwili gdy uda&#x142;o mu si&#x119; pozytywnie przej&#x15B;&#x107; jaki&#x15B; proces</li><li><a href="https://pub.dev/packages/awesome_notifications?ref=lsdev.pl">awesome_notifications</a> - pakiet umo&#x17C;liwia odbieranie i tworzenie zaawansowanych powiadomie&#x144; typu push notification</li></ul><h3 id="trendy">Trendy</h3><p>Flutter na prze&#x142;omie ostatnich 5 lat stale pnie si&#x119; na szczyt listy popularno&#x15B;ci multiplatformowych narz&#x119;dzi. Na pozycji lidera znajduje si&#x119; ju&#x17C; niekwestionowanie od 2 lat i nic nie wskazuje na to by jego popularno&#x15B;&#x107; w najbli&#x17C;szej przysz&#x142;o&#x15B;ci mia&#x142;a si&#x119; zmniejszy&#x107;.</p><figure class="kg-card kg-image-card"><img src="https://lsdev.pl/content/images/2022/06/trends.webp" class="kg-image" alt="Flutter, narz&#x119;dzie do tworzenia natywnych aplikacji mobilnych" loading="lazy" width="1438" height="848" srcset="https://lsdev.pl/content/images/size/w600/2022/06/trends.webp 600w, https://lsdev.pl/content/images/size/w1000/2022/06/trends.webp 1000w, https://lsdev.pl/content/images/2022/06/trends.webp 1438w" sizes="(min-width: 720px) 720px"></figure><h3 id="wady">Wady</h3><p>Oczywi&#x15B;cie jak ka&#x17C;de gotowe narz&#x119;dzie ma te&#x17C; swoje bol&#x105;czki. Na szcz&#x119;&#x15B;cie tych nie ma du&#x17C;o i w&#x142;a&#x15B;ciwie to musia&#x142;em si&#x119; d&#x142;ugo zastanawia&#x107; by czym&#x15B; zape&#x142;ni&#x107; t&#x119; sekcj&#x119;. Napisa&#x142;em wy&#x17C;ej, &#x17C;e w frameworku Flutter na prawd&#x119; szybko wytwarza si&#x119; oprogramowanie pod warunkiem, &#x17C;e znamy ju&#x17C; j&#x119;zyk Dart. Pury&#x15B;ci czy to od strony Androida czy to iOS mog&#x105; by&#x107; zawiedzeni faktem, &#x17C;e dost&#x119;p do SDK natywnej platformy jest ograniczony.</p>]]></content:encoded></item><item><title><![CDATA[Docker w praktyce. Dobre praktyki i przydatne polecenia]]></title><description><![CDATA[<p><a href="https://www.docker.com/?ref=lsdev.pl">Docker</a> jest przydatnym narz&#x119;dziem s&#x142;u&#x17C;&#x105;cym do wirtualizacji na poziomie systemu operacyjnego. Oprogramowanie to pozwala na uruchamianie proces&#xF3;w aplikacji w &quot;lekkich&quot; kontenerach odizolowanych od systemu operacyjnego hosta. W pewnym sensie kontenery przypominaj&#x105; maszyny wirtualne jednak zakres izolacji obs&#x142;</p>]]></description><link>https://lsdev.pl/posts/docker-w-praktyce/</link><guid isPermaLink="false">628cfc18a8239d15d9e07836</guid><category><![CDATA[Docker]]></category><category><![CDATA[Java]]></category><dc:creator><![CDATA[Łukasz]]></dc:creator><pubDate>Wed, 25 May 2022 14:07:00 GMT</pubDate><media:content url="https://lsdev.pl/content/images/2022/05/docker.webp" medium="image"/><content:encoded><![CDATA[<img src="https://lsdev.pl/content/images/2022/05/docker.webp" alt="Docker w praktyce. Dobre praktyki i przydatne polecenia"><p><a href="https://www.docker.com/?ref=lsdev.pl">Docker</a> jest przydatnym narz&#x119;dziem s&#x142;u&#x17C;&#x105;cym do wirtualizacji na poziomie systemu operacyjnego. Oprogramowanie to pozwala na uruchamianie proces&#xF3;w aplikacji w &quot;lekkich&quot; kontenerach odizolowanych od systemu operacyjnego hosta. W pewnym sensie kontenery przypominaj&#x105; maszyny wirtualne jednak zakres izolacji obs&#x142;ugiwany przez <em>dockera</em> jest zdecydowanie mniejszy.</p><p>Kontenery w przeciwie&#x144;stwie do maszyn wirtualnych korzystaj&#x105; z tego samego j&#x105;dra a izolacja jest zaimplementowana w ramach tego pojedynczego j&#x105;dra. Nazywamy to wirtualizacj&#x105; systemu operacyjnego. Z kolei maszyny wirtualne s&#x105; ci&#x119;&#x17C;kie i zasobo&#x17C;erne w por&#xF3;wnaniu do kontener&#xF3;w z uwagi na fakt, i&#x17C; ka&#x17C;da ich nowa instancja jest r&#xF3;wnoznaczna z now&#x105; instancj&#x105; j&#x105;dra.</p><figure class="kg-card kg-image-card"><img src="https://lsdev.pl/content/images/2022/05/architecture.webp" class="kg-image" alt="Docker w praktyce. Dobre praktyki i przydatne polecenia" loading="lazy" width="1920" height="972" srcset="https://lsdev.pl/content/images/size/w600/2022/05/architecture.webp 600w, https://lsdev.pl/content/images/size/w1000/2022/05/architecture.webp 1000w, https://lsdev.pl/content/images/size/w1600/2022/05/architecture.webp 1600w, https://lsdev.pl/content/images/2022/05/architecture.webp 1920w" sizes="(min-width: 720px) 720px"></figure><p>Wymienione technologie tj. wirtualizacja i konteneryzacja s&#x105; nieco odmienne w zwi&#x105;zku z tym ich zastosowanie b&#x119;dzie r&#xF3;wnie&#x17C; nieco inne.</p><p><strong>Maszyny wirtualne</strong> wykorzystamy gdy chcemy mie&#x107; pe&#x142;n&#x105; izolacj&#x119; od systemu operacyjnego hosta jak r&#xF3;wnie&#x17C; maj&#x105;c potrzeb&#x119; uruchomienia wielu r&#xF3;&#x17C;nych system&#xF3;w operacyjnych na tym samym komputerze. Maszyny wirtualne z za&#x142;o&#x17C;enia zast&#x119;puj&#x105; rzeczywisty sprz&#x119;t, kt&#xF3;ry mo&#x17C;na postawi&#x107; w szafie na kilka lat. Poniewa&#x17C; zast&#x119;puj&#x105; one prawdziwy serwer cz&#x119;sto z za&#x142;o&#x17C;enia s&#x105; utrzymywane przez d&#x142;ugi czas.</p><p><strong>Kontenery</strong> wykorzystamy gdy chcemy uruchamia&#x107; nasze aplikacje w &quot;lekkich opakowaniach&quot; w sytuacjach gdy tworzenie kolejnej maszyny wirtualnej by&#x142;oby zbyt kosztowne. Przyk&#x142;adowo nie uruchomiliby&#x15B;my maszyny wirtualnej aby wystartowa&#x107; w niej pojedynczy proces np. <em>curl</em> albo <em>ffmpeg </em>w celu uzyskania wyniku poniewa&#x17C; by&#x142;oby to bardzo czasoch&#x142;onne i wymaga&#x142;oby uruchomienia ca&#x142;ego systemu operacyjnego w celu wykonania jednego polecenia. Kontenery natomiast idealnie nadaj&#x105; si&#x119; do tego typu zastosowa&#x144; a jedn&#x105; z najwi&#x119;kszych korzy&#x15B;ci z ich zastosowania jest dobre wykorzystanie zasob&#xF3;w poniewa&#x17C; nie jest potrzebny ca&#x142;y system operacyjny dla ka&#x17C;dej wyizolowanej funkcji. Kontenery r&#xF3;wnie&#x17C; znajd&#x105; zastosowanie do szybkiego stawiania lokalnych &#x15B;rodowisk np. przy u&#x17C;yciu <a href="https://docs.docker.com/compose/?ref=lsdev.pl">Docker Compose</a>.</p><p>Jednak by&#x107; mo&#x17C;e zastanawiasz si&#x119; w jakim celu pakowa&#x107; swoje aplikacje w kontenery? Po co jest ta kolejna warstwa? Czemu nie instalowa&#x107; aplikacji bezpo&#x15B;rednio na serwerze? Przecie&#x17C; <em>Java</em> ju&#x17C; wykonuje si&#x119; na maszynie wirtualnej po co wi&#x119;c ta kolejna abstrakcja?</p><p>Jedn&#x105; z najwa&#x17C;niejszych silnych stron <em>dockera</em> jest jego zdolno&#x15B;&#x107; do odseparowania aplikacji od sprz&#x119;tu i systemu operacyjnego serwera, na kt&#xF3;rym dzia&#x142;a dzi&#x119;ki czemu Twoja aplikacja nie jest przywi&#x105;zana do &#x17C;adnego konkretnego komputera czy &#x15B;rodowiska. Z <em>dockerem </em>dostarczasz aplikacj&#x119; wraz ze wszystkimi plikami niezb&#x119;dnymi do jej uruchomienia w postaci obrazu lub artefaktu, z kt&#xF3;rego p&#xF3;&#x17A;niej tworzone s&#x105; dzia&#x142;aj&#x105;ce kontenery. W celu wdro&#x17C;enia aplikacji na dane &#x15B;rodowisko nie musisz na nim instalowa&#x107; zale&#x17C;no&#x15B;ci czy te&#x17C; bibliotek, od kt&#xF3;rych aplikacja zale&#x17C;y poniewa&#x17C; wszystkie zale&#x17C;no&#x15B;ci s&#x105; ju&#x17C; w &#x15B;rodku w obrazie. Pozwala to nie tylko horyzontalnie skalowa&#x107; aplikacje w prosty spos&#xF3;b u&#x17C;ywaj&#x105;c r&#xF3;&#x17C;nych platform do orkiestracji np. Kubernetes lub Swarm, ale r&#xF3;wnie&#x17C; unikn&#x105;&#x107; dziwnych b&#x142;&#x119;d&#xF3;w spowodowanych r&#xF3;&#x17C;nicami w konfiguracji mi&#x119;dzy &#x15B;rodowiskami poniewa&#x17C; aplikacja nie zale&#x17C;y od zewn&#x119;trznego &#x15B;rodowiska.</p><h2 id="architektura">Architektura</h2><p>W du&#x17C;ym uproszczeniu <strong>Docker</strong> sk&#x142;ada si&#x119; z trzech komponent&#xF3;w:</p><ul><li><em>docker</em> - klient serwera czyli warstwa, z kt&#xF3;r&#x105; u&#x17C;ytkownik ko&#x144;cowy ma najwi&#x119;ksz&#x105; styczno&#x15B;&#x107;</li><li><em>dockerd</em> - serwer, demon dzia&#x142;aj&#x105;cy w tle, kt&#xF3;ry zarz&#x105;dza kontenerami i obs&#x142;uguje &#x17C;&#x105;dania od klienta</li><li><em>rejestry</em> - repozytoria, z kt&#xF3;rych Docker zaci&#x105;ga obrazy np. <a href="https://hub.docker.com/?ref=lsdev.pl">Docker Hub</a></li></ul><figure class="kg-card kg-image-card"><img src="https://lsdev.pl/content/images/2022/05/docker-arch.svg" class="kg-image" alt="Docker w praktyce. Dobre praktyki i przydatne polecenia" loading="lazy" width="1009" height="527"></figure><p>Warto wspomnie&#x107;, &#x17C;e proces <em>dockerd</em> mo&#x17C;na uruchomi&#x107; natywnie tylko w systemie Linux. Je&#x17C;eli uruchamiamy Dockera w systemach Windows lub MacOS to wykorzystamy wirtualn&#x105; maszyn&#x119; z uruchomionym systemem Linux aby uruchomi&#x107; <em>dockerd</em> - serwer Dockera.</p><p>Ka&#x17C;dy kontener powstaje z obrazu podobnie jak wirtualny dysk staje si&#x119; maszyn&#x105; wirtualn&#x105; po jej uruchomieniu. W pracy z Dockerem wykorzystujemy gotowe obrazy pobrane z zdalnego repozytorium lub tworzymy w&#x142;asne z wykorzystaniem pliku <em><a href="https://docs.docker.com/engine/reference/builder/?ref=lsdev.pl">Dockerfile</a></em>.</p><h2 id="plik-dockerfile">Plik Dockerfile</h2><p>Plik, kt&#xF3;ry opisuje wszystkie kroki niezb&#x119;dne do wykonania aby utworzy&#x107; jeden obraz. Zazwyczaj powinien by&#x107; umieszczony w g&#x142;&#xF3;wnym katalogu repozytorium aplikacji, kt&#xF3;r&#x105; chcemy umie&#x15B;ci&#x107; w obrazie.</p><p>Poni&#x17C;ej przyk&#x142;adowy plik <em>Dockerfile. </em>W komentarzach zawar&#x142;em wyja&#x15B;nienia poszczeg&#xF3;lnych komend.</p><!--kg-card-begin: html--><pre><code class="language-bash line-numbers">FROM java:8  			# obraz bazowy (sk&#x142;ada si&#x119; z wielu warstw tj. obraz&#xF3;w przej&#x15B;ciowych)
COPY . /var/www/java    # kopiuje bie&#x17C;&#x105;cy katalog do /var/www/java w kontenerze
WORKDIR /var/www/java   # ustawia katalog bie&#x17C;&#x105;cy w kontenerze
RUN javac Hello.java    # uruchamia kompilacj&#x119; programu
CMD [&quot;java&quot;, &quot;Hello&quot;]   # uruchamia program
</code></pre><!--kg-card-end: html--><p>Na podstawie tak przygotowanego pliku mo&#x17C;emy spr&#xF3;bowa&#x107; zbudowa&#x107; obraz a nast&#x119;pnie na jego podstawie uruchomi&#x107; instancj&#x119; kontenera.</p><!--kg-card-begin: html--><pre><code class="language-bash line-numbers">&#x276F; docker build . -t hello-java
[+] Building 15.4s (10/10) FINISHED
 =&gt; [internal] load build definition from Dockerfile                                                                 
 =&gt; [1/4] FROM docker.io/library/java:8@sha256:c1ff613e8ba25833d2e1940da0940c3824f03f802c449f3d1815a66b7f8c0e9d                                                 10.5s
 =&gt; =&gt; resolve docker.io/library/java:8@sha256:c1ff613e8ba25833d2e1940da0940c3824f03f802c449f3d1815a66b7f8c0e9d                                                  0.0s
 =&gt; =&gt; sha256:76610ec20bf5892e24cebd4153c7668284aa1d1151b7c3b0c7d50c579aa5ce75 42.50MB / 42.50MB                                                                 2.0s
...
 =&gt; [2/4] COPY . /var/www/java                                                                                                                                   0.1s
 =&gt; [3/4] WORKDIR /var/www/java                                                                                                                                  0.0s
 =&gt; [4/4] RUN javac Hello.java                                                                                                                                   2.8s
 =&gt; exporting to image                                                                                                                                           0.0s
 =&gt; =&gt; exporting layers                                                                                                                                          0.0s
 =&gt; =&gt; writing image sha256:e772e4db060862c73991141dc7dc57c7d86655ff2d1685954d865b5debc857c7                                                                     0.0s
 =&gt; =&gt; naming to docker.io/library/hello-java                                                                                                                    0.0s

&#x276F; docker run hello-java
Hello world
</code></pre><!--kg-card-end: html--><p>Obraz dockerowy jest zbudowany z warstw nak&#x142;adaj&#x105;cych si&#x119; na siebie. Ka&#x17C;da komenda w pliku Dockerfile dodaje kolejn&#x105; warstw&#x119;. Podczas tego procesu docker uruchamia kontener na bazie obrazu z warstwy ni&#x17C;ej nast&#x119;pnie wykonuje zadan&#x105; operacj&#x119; po czym zapisuje stan systemu plik&#xF3;w jako nowy obraz. Ze wzgl&#x119;du na to, &#x17C;e Docker cache&apos;uje powsta&#x142;e warstwy aby przy&#x15B;pieszy&#x107; budowanie obrazu, je&#x17C;eli dokonamy zmian w pliku <em>Dockerfile</em> &#xA0;i przebudujemy obraz Docker przebuduje tylko te warstwy (obrazy po&#x15B;rednie), kt&#xF3;re si&#x119; zmieni&#x142;y i wy&#x17C;sze poniewa&#x17C; wy&#x17C;sze bazuj&#x105; na ni&#x17C;szych wi&#x119;c zmiana tych ni&#x17C;szych mog&#x142;a wp&#x142;yn&#x105;&#x107; na wy&#x17C;sze warstwy. Z tego wzgl&#x119;du dobr&#x105; praktyk&#x105; jest umieszczanie jak najni&#x17C;ej pliku polece&#x144; bazuj&#x105;cych na zasobach, kt&#xF3;re cz&#x119;sto si&#x119; zmieniaj&#x105;.</p><h2 id="uruchamianie-obraz%C3%B3w">Uruchamianie obraz&#xF3;w</h2><p>Jak widzieli&#x15B;my na przyk&#x142;adzie wy&#x17C;ej obrazy uruchamiamy z wykorzystaniem komendy <code>docker run &lt;image&gt;</code>. W ten spos&#xF3;b Docker utworzy&#x142; na jego podstawie nowy kontener przypisuj&#x105;c mu unikalny identyfikator. Za ka&#x17C;dym razem gdy uruchomimy <code>docker run</code> stworzymy now&#x105; instancj&#x119; kontenera podobnie jak tworzymy now&#x105; instancj&#x119; klasy za pomoc&#x105; operatora <code>new</code> w programowaniu obiektowym. Mo&#x17C;emy wykona&#x107; kilka prostych komend aby przekona&#x107; si&#x119;, &#x17C;e faktycznie tak si&#x119; dzieje. W tym celu pos&#x142;u&#x17C;&#x119; si&#x119; obrazem <a href="https://hub.docker.com/_/ubuntu?ref=lsdev.pl">Ubuntu</a>.</p><!--kg-card-begin: html--><pre><code class="language-bash line-numbers">&#x276F; docker run ubuntu:latest whoami
root
&#x276F; docker run ubuntu:latest uname -a
Linux 2c410a1519fb 5.10.104-linuxkit #1 SMP PREEMPT Thu Mar 17 17:05:54 UTC 2022 aarch64 aarch64 aarch64 GNU/Linux
&#x276F; docker ps -a | head -3
CONTAINER ID   IMAGE            COMMAND                  CREATED              STATUS                          PORTS                               NAMES
2c410a1519fb   ubuntu:latest    &quot;uname -a&quot;               17 seconds ago       Exited (0) 16 seconds ago                                           distracted_napier
89f9a750d6e2   ubuntu:latest    &quot;whoami&quot;                 26 seconds ago       Exited (0) 25 seconds ago                                           nostalgic_yalow
</code></pre><!--kg-card-end: html--><p>Powy&#x17C;szy test potwierdza, &#x17C;e wykonanie dw&#xF3;ch polece&#x144; u&#x17C;ywaj&#x105;c komendy <code>docker run</code> faktycznie spowodowa&#x142;o stworzenie dw&#xF3;ch r&#xF3;&#x17C;nych kontener&#xF3;w z unikalnymi identyfikatorami, kt&#xF3;re zako&#x144;czy&#x142;y si&#x119; po wykonaniu tych polece&#x144;.</p><p>Czas &#x17C;ycia kontenera jest zdefiniowany przez proces, kt&#xF3;ry jest uruchomiony w &#x15B;rodku. Je&#x17C;eli proces, kt&#xF3;ry &quot;podtrzymuje&quot; kontener zako&#x144;czy dzia&#x142;anie kontener r&#xF3;wnie&#x17C; si&#x119; zako&#x144;czy. Mo&#x17C;emy zobaczy&#x107; to zachowanie w praktyce u&#x17C;ywaj&#x105;c komendy <code>sleep</code>.</p><!--kg-card-begin: html--><pre><code class="language-bash">&#x276F; docker run ubuntu:latest sleep 5
</code></pre><!--kg-card-end: html--><p>Nowa instancja kontenera zosta&#x142;a stworzona po czym wystartowa&#x142; proces <code>sleep</code>, kt&#xF3;ry zamrozi&#x142; konsol&#x119; na 5 sekund nast&#x119;pnie proces ten zako&#x144;czy&#x142; swoje dzia&#x142;anie a zaraz po nim kontener. Zako&#x144;czony kontener zobaczymy wykonuj&#x105;c poni&#x17C;sz&#x105; komend&#x119;, kt&#xF3;ra listuje wszystkie kontenery w systemie.</p><!--kg-card-begin: html--><pre><code class="language-bash line-numbers">&#x276F; docker ps -a

CONTAINER ID   IMAGE            COMMAND                  CREATED              STATUS                          PORTS                               NAMES
4f6d73900753   ubuntu:latest    &quot;sleep 5&quot;                13 seconds ago       Exited (0) 7 seconds ago
</code></pre><!--kg-card-end: html--><p>Nie musimy za ka&#x17C;dym razem tworzy&#x107; nowej instancji kontenera. Mo&#x17C;emy wykorzysta&#x107; polecenie <code>docker start</code> aby wystartowa&#x107; istniej&#x105;cy zako&#x144;czony kontener i <code>docker attach</code> aby si&#x119; do niego pod&#x142;&#x105;czy&#x107; i obserwowa&#x107; standardowe wyj&#x15B;cie procesu, kt&#xF3;ry jest uruchomiony.</p><!--kg-card-begin: html--><pre><code class="language-bash line-numbers">&#x276F; docker start 4f6d73900753
&#x276F; docker attach 4f6d73900753
</code></pre><!--kg-card-end: html--><p>Uruchomiony dzia&#x142;aj&#x105;cy w tle kontener mo&#x17C;emy r&#xF3;wnie&#x17C; w prosty spos&#xF3;b zrestartowa&#x107;.</p><!--kg-card-begin: html--><pre><code class="language-bash">&#x276F; docker restart 4f6d73900753
</code></pre><!--kg-card-end: html--><p><strong>Uruchamianie kontenera w tle</strong></p><p>Podaj&#x105;c parametr <code>-d</code> mo&#x17C;emy uruchomi&#x107; kontener w tle:</p><!--kg-card-begin: html--><pre><code class="language-bash">&#x276F; docker run -d ubuntu:latest sh -c &quot;while true; do date; sleep 1; done&quot;
</code></pre><!--kg-card-end: html--><p>Nast&#x119;pnie mo&#x17C;emy si&#x119; do niego pod&#x142;&#x105;czy&#x107; i obserwowa&#x107; standardowe wyj&#x15B;cie:</p><!--kg-card-begin: html--><pre><code class="language-bash line-numbers">&#x276F; docker attach 4e8c73d5487a
Tue May 24 11:50:44 UTC 2022
Tue May 24 11:50:45 UTC 2022
Tue May 24 11:50:46 UTC 2022
...
</code></pre><!--kg-card-end: html--><p>Aby zako&#x144;czy&#x107; sesje z kontenerem mo&#x17C;emy uruchomi&#x107; <code>ctrl + c</code> jednak to zastopuje kontener. Je&#x17C;eli chcemy tego unikn&#x105;&#x107; mo&#x17C;emy pod&#x142;&#x105;czy&#x107; si&#x119; do kontenera z opcj&#x105; <code>--sig-proxy=false</code>.</p><!--kg-card-begin: html--><pre><code class="language-bash line-numbers">&#x276F; docker attach --sig-proxy=false 4e8c73d5487a
Tue May 24 11:50:44 UTC 2022
Tue May 24 11:50:45 UTC 2022
Tue May 24 11:50:46 UTC 2022
...
</code></pre><!--kg-card-end: html--><p>Po czym gdy uruchomimy <code>ctrl + c</code> kontener dalej b&#x119;dzie dzia&#x142;a&#x142; w tle. Natomiast w celu samego obserwowania log&#xF3;w lepszym pomys&#x142;em b&#x119;dzie komenda <code>docker logs</code> om&#xF3;wiona p&#xF3;&#x17A;niej.</p><p><strong>Przekazanie zmiennych &#x15B;rodowiskowych podczas uruchamiania kontenera</strong></p><p>Dobr&#x105; praktyk&#x105; jest aby nie przechowywa&#x107; hase&#x142; czy te&#x17C; innych danych wra&#x17C;liwych w obrazach. Zamiast tego lepiej je wstrzykiwa&#x107; do kontenera podczas jego uruchamiania w nast&#x119;puj&#x105;cy spos&#xF3;b.</p><!--kg-card-begin: html--><pre><code class="language-bash">&#x276F; docker run -d -e APP_ENV=production -e APP_SECRET=DACFC787 myapp:latest
</code></pre><!--kg-card-end: html--><h3 id="usuwanie-kontener%C3%B3w">Usuwanie kontener&#xF3;w</h3><p>Domy&#x15B;lnie zako&#x144;czone kontenery nie s&#x105; usuwane przez Dockera. Je&#x17C;eli chcemy uruchomi&#x107; w kontenerze tylko jedno polecenie i po jego zako&#x144;czeniu pozby&#x107; si&#x119; kontenera bez konieczno&#x15B;ci usuwania go r&#x119;cznie mo&#x17C;emy skorzysta&#x107; z opcji <code>--rm</code>.</p><!--kg-card-begin: html--><pre><code class="language-bash">&#x276F; docker run --rm ubuntu:latest ls -la
</code></pre><!--kg-card-end: html--><p>Usuni&#x119;cie kontenera, kt&#xF3;ry dzia&#x142;a w tle wygl&#x105;da nast&#x119;puj&#x105;co.</p><!--kg-card-begin: html--><pre><code class="language-bash">&#x276F; docker stop 4e8c73d5487a &amp;&amp; docker rm 4e8c73d5487a
</code></pre><!--kg-card-end: html--><h3 id="tryb-interaktywny">Tryb interaktywny</h3><p>Z poprzednich rozwa&#x17C;a&#x144; dowiedzieli&#x15B;my si&#x119;, &#x17C;e kontener <em>dockera</em> zako&#x144;czy si&#x119; je&#x17C;eli nie b&#x119;dzie procesu, kt&#xF3;ry b&#x119;dzie go &quot;podtrzymywa&#x142;&quot;. Je&#x17C;eli jednak mamy potrzeb&#x119; pod&#x142;&#x105;czy&#x107; si&#x119; do konsoli w celu eksploracji systemu plik&#xF3;w (przydaje si&#x119; podczas debugowania) mo&#x17C;emy to osi&#x105;gn&#x105;&#x107; uruchamiaj&#x105;c kontener w trybie interaktywnym w nast&#x119;puj&#x105;cy spos&#xF3;b.</p><!--kg-card-begin: html--><pre><code class="language-bash">&#x276F; docker run -it ubuntu:latest /bin/bash
</code></pre><!--kg-card-end: html--><p>lub</p><!--kg-card-begin: html--><pre><code class="language-bash">&#x276F; docker run -it --entrypoint /bin/bash ubuntu:latest
</code></pre><!--kg-card-end: html--><p>Je&#x17C;eli chcemy pod&#x142;&#x105;czy&#x107; si&#x119; do ju&#x17C; istniej&#x105;cego kontenera w spos&#xF3;b interaktywny mo&#x17C;emy wykorzysta&#x107; polecenie <code>docker exec</code>.</p><!--kg-card-begin: html--><pre><code class="language-bash">&#x276F; docker exec -it 4e8c73d5487a /bin/bash
</code></pre><!--kg-card-end: html--><h3 id="tworzenie-obrazu-na-podstawie-istniej%C4%85cego-kontenera">Tworzenie obrazu na podstawie istniej&#x105;cego kontenera</h3><p>W trybie interaktywnym, kt&#xF3;ry by&#x142; poruszony w poprzedniej sekcji mo&#x17C;emy bez problemu dokona&#x107; dowolnych zmian np. zmieni&#x107; konfiguracj&#x119; czy te&#x17C; zainstalowa&#x107; potrzebne nam pakiety i zale&#x17C;no&#x15B;ci. Mamy mo&#x17C;liwo&#x15B;&#x107; niejako zapisania tych zmian tworz&#x105;c nowy obraz na podstawie zmian, kt&#xF3;rych dokonali&#x15B;my.</p><!--kg-card-begin: html--><pre><code class="language-bash">&#x276F; docker commit 4e8c73d5487a testimage:1.0
</code></pre><!--kg-card-end: html--><p>Jednak lepszym i rekomendowanym sposobem na tworzenie obraz&#xF3;w jest plik <em>Dockerfile</em> om&#xF3;wiony wcze&#x15B;niej.</p><h3 id="wyeksportowanie-systemu-plik%C3%B3w-kontenera">Wyeksportowanie systemu plik&#xF3;w kontenera</h3><p>Wykorzystuj&#x105;c polecenie <code>docker export</code> mo&#x17C;emy w prosty spos&#xF3;b &quot;zrzuci&#x107;&quot; aktualny stan systemu plik&#xF3;w kontenera np. w celu jego eksploracji w wygodnym dla nas narz&#x119;dziu i debugowania.</p><!--kg-card-begin: html--><pre><code class="language-bash line-numbers">&#x276F; docker export 4e8c73d5487a -o exported.tar
&#x276F; tar -tvf exported.tar | head
-rwxr-xr-x  0 0      0           0 May 24 13:50 .dockerenv
lrwxrwxrwx  0 0      0           0 Apr 28 13:55 bin -&gt; usr/bin
drwxr-xr-x  0 0      0           0 Apr 18 12:28 boot/
drwxr-xr-x  0 0      0           0 May 24 13:50 dev/
-rwxr-xr-x  0 0      0           0 May 24 13:50 dev/console
drwxr-xr-x  0 0      0           0 May 24 13:50 dev/pts/
drwxr-xr-x  0 0      0           0 May 24 13:50 dev/shm/
drwxr-xr-x  0 0      0           0 May 24 13:50 etc/
-rw-------  0 0      0           0 Apr 28 13:56 etc/.pwd.lock
-rw-r--r--  0 0      0        3028 Apr 28 13:56 etc/adduser.conf
</code></pre><!--kg-card-end: html--><h3 id="tworzenie-archiwum-tar-z-obrazem">Tworzenie archiwum tar z obrazem</h3><p>Mo&#x17C;esz spotka&#x107; si&#x119; z sytuacj&#x105;, &#x17C;e maszyna, na kt&#xF3;rej chcesz uruchomi&#x107; obraz nie ma dost&#x119;pu do internetu w zwi&#x105;zku z tym nie b&#x119;dzie w stanie pobra&#x107; tego obrazu z zdalnego repozytorium obraz&#xF3;w. Z pomoc&#x105; przychodzi polecenie <code>docker save</code>, kt&#xF3;re wyeksportuje nam obraz do archiwum tar.</p><!--kg-card-begin: html--><pre><code class="language-bash">&#x276F; docker save -o ubuntu:latest.tar ubuntu:latest
</code></pre><!--kg-card-end: html--><p>Nast&#x119;pnie przenosimy archiwum na drug&#x105; maszyn&#x119; z zainstalowanym dockerem i &#x142;adujemy wykorzystuj&#x105;c polecenie <code>docker load</code>.</p><!--kg-card-begin: html--><pre><code class="language-bash">&#x276F; docker load -i ubuntu:latest.tar 
</code></pre><!--kg-card-end: html--><h3 id="cmd-vs-entrypoint">CMD vs ENTRYPOINT</h3><p>W sekcji dot. pliku <em>Dockerfile</em> u&#x17C;yli&#x15B;my nast&#x119;puj&#x105;cej komendy.</p><!--kg-card-begin: html--><pre><code class="language-bash">CMD [&quot;java&quot;, &quot;Hello&quot;]
</code></pre><!--kg-card-end: html--><p>Powy&#x17C;sze polecenie jest informacj&#x105; dla Dockera jaki proces a w zasadzie polecenie chcemy uruchomi&#x107; wewn&#x105;trz kontenera gdy ten startuje. Podczas gdy uruchomili&#x15B;my kontener u&#x17C;ywaj&#x105;c polecenia <code>docker run java-hello</code> Docker uruchomi&#x142; polecenie wskazane przez nas w CMD tj. java. Mo&#x17C;emy r&#xF3;wnie&#x17C; napisa&#x107; polecenie jakie ma by&#x107; wykonane gdy startujemy kontener.</p><!--kg-card-begin: html--><pre><code class="language-bash">&#x276F; docker run java-hello ls -la
</code></pre><!--kg-card-end: html--><p>Przekazuj&#x105;c polecenie podczas uruchamiania kontenera nadpisali&#x15B;my te domy&#x15B;lnie, kt&#xF3;re zapisali&#x15B;my w pliku <em>Dockerfile</em> na samym ko&#x144;cu. W tym momencie zamiast programu w Java na standardowe wej&#x15B;cie zostanie wylistowany bie&#x17C;&#x105;cy katalog.</p><p>Polecenie ENTRYPOINT jest w pewnym sensie podobne do CMD. Domy&#x15B;lnie ENTRYPOINT jest ustawiony na warto&#x15B;&#x107; <code>sh -c</code> jednak mo&#x17C;na zmieni&#x107; t&#x119; warto&#x15B;&#x107;. CMD nie ma domy&#x15B;lnej warto&#x15B;ci wi&#x119;c trzeba j&#x105; wskaza&#x107;. Podaj&#x105;c w CMD warto&#x15B;&#x107; <code>ls -la</code> tak naprawd&#x119; Docker najpierw patrzy na zmienn&#x105; ENTRYPOINT i domy&#x15B;lnie uruchamia proces pow&#x142;oki <code>sh -c</code> a jako parametr podaje polecenie podane w CMD. W efekcie wykonywane jest polecenie <code>sh -c ls -la</code> . Dlatego upraszczaj&#x105;c mo&#x17C;na powiedzie&#x107;, &#x17C;e warto&#x15B;ci podane w CMD s&#x105; argumentami dla programu wskazanego w ENTRYPOINT, kt&#xF3;ry zostanie uruchomiony. Warto zmieni&#x107; domy&#x15B;ln&#x105; warto&#x15B;&#x107; dla ENTRYPOINT na proces aplikacji, kt&#xF3;ry chcemy docelowo uruchomi&#x107; w kontenerze gdy&#x17C; uruchamianie dodatkowo pow&#x142;oki raczej nie ma wi&#x119;kszego zastosowania chyba, &#x17C;e aplikacj&#x119; startujemy skryptem pow&#x142;oki.</p><h3 id="przekierowanie-standardowych-strumieni-kontenera">Przekierowanie standardowych strumieni kontenera</h3><p>Warto wiedzie&#x107;, &#x17C;e Docker mapuje na zewn&#x105;trz standardowe strumienie tj. STDOUT i STDERR a tak&#x17C;e kody wyj&#x15B;ciowe procesu uruchomionego w kontenerze.</p><!--kg-card-begin: html--><pre><code class="language-bash line-numbers">&#x276F; docker run --rm ubuntu:latest cat /etc/passwd &gt; pass
&#x276F; echo $?
0
</code></pre><!--kg-card-end: html--><p>Mo&#x17C;e mie&#x107; to szerokie zastosowanie w aplikacjach, kt&#xF3;re dynamicznie uruchamiaj&#x105; kontenery na &#x17C;&#x105;danie u&#x17C;ytkownika i zaczytuj&#x105; rezultat z STDOUT. Uruchamianie polece&#x144; bezpo&#x15B;rednio na serwerze nie by&#x142;oby dobrym pomys&#x142;em z punktu widzenia bezpiecze&#x144;stwa natomiast wykonanie tego w kontenerze jest zdecydowanie lepszym rozwi&#x105;zaniem poniewa&#x17C; dzi&#x119;ki izolacji polecenie w &#x17C;aden spos&#xF3;b nie zaszkodzi &#x15B;rodowisku wykonawczemu hosta.</p><h3 id="logi">Logi</h3><p>W celu pobrania wszystkich log&#xF3;w z kontenera wykonujemy nast&#x119;puj&#x105;c&#x105; komend&#x119; przekazuj&#x105;c w parametrze identyfikator kontenera.</p><!--kg-card-begin: html--><pre><code class="language-bash">&#x276F; docker logs 4e8c73d5487a
</code></pre><!--kg-card-end: html--><p>Aby widzie&#x107; logi w trybie &#x15B;ledz&#x105;cym u&#x17C;ywamy parametru <code>-f</code></p><!--kg-card-begin: html--><pre><code class="language-bash">&#x276F; docker logs -f 4e8c73d5487a
</code></pre><!--kg-card-end: html--><p>Mamy r&#xF3;wnie&#x17C; mo&#x17C;liwo&#x15B;&#x107; filtrowania log&#xF3;w w r&#xF3;&#x17C;ny spos&#xF3;b.</p><!--kg-card-begin: html--><pre><code class="language-bash line-numbers">&#x276F; docker logs --since=1h 4e8c73d5487a // logi z ostatniej godziny
&#x276F; docker logs --since=3m -f 4e8c73d5487a // logi z ostatnich 3 minut w trybie &#x15B;ledz&#x105;cym
&#x276F; docker logs --tail=20 -f 4e8c73d5487a // ostatnie 20 linni w trybie &#x15B;ledz&#x105;cym
</code></pre><!--kg-card-end: html--><h3 id="woluminy">Woluminy</h3><p>R&#xF3;&#x17C;ne aplikacje cz&#x119;sto zapisuj&#x105; stan do systemu plik&#xF3;w. Aby mie&#x107; dost&#x119;p do danych aplikacji i zachowa&#x107; stan pomi&#x119;dzy restartowaniem kontenera (np. aktualizacja aplikacji do nowej wersji) mo&#x17C;emy u&#x17C;y&#x107; wolumin&#xF3;w. Dzi&#x119;ki nim mamy mo&#x17C;liwo&#x15B;&#x107; zmapowania katalogu hosta i zamontowania go pod kontener <em>dockera</em>. W celu definicji woluminu dla kontenera mo&#x17C;emy u&#x17C;y&#x107; parametru <code>-v</code>.</p><!--kg-card-begin: html--><pre><code class="language-bash line-numbers">&#x276F; docker run -v $(pwd):/tmp/test --rm ubuntu:latest bash -c &quot;echo &apos;Hello&apos; &gt; /tmp/test/file.txt&quot;
&#x276F; cat file.txt
Hello
</code></pre><!--kg-card-end: html--><p>Dlaczego nie mo&#x17C;emy zdefiniowa&#x107; tego mapowania w pliku <em>Dockerfile</em>?<em> </em>Poniewa&#x17C; plik <em>Dockerfile</em> zawiera tylko komendy dot. docelowego obrazu odpowiedzialne za poprawne zbudowanie tego obrazu. Zewn&#x119;trzne zasoby, mapowania systemu plik&#xF3;w, port&#xF3;w itd. s&#x105; elementami na styku obrazu i &#x15B;rodowiska, na kt&#xF3;rym ten obraz chcemy wdro&#x17C;y&#x107; i uruchomi&#x107; w postaci kontenera. Z pewno&#x15B;ci&#x105; nie chcemy aby nasz obraz mocno zale&#x17C;a&#x142; od tego &#x15B;rodowiska.</p><h3 id="pod%C5%82%C4%85czanie-si%C4%99-do-us%C5%82ug-hosta-wewn%C4%85trz-kontenera">Pod&#x142;&#x105;czanie si&#x119; do us&#x142;ug hosta wewn&#x105;trz kontenera</h3><p>W przypadku gdy mamy na ho&#x15B;cie zainstalowan&#x105; jak&#x105;&#x15B; us&#x142;ug&#x119; np. mysql po&#x142;&#x105;czenie si&#x119; z ni&#x105; w &#x15B;rodku kontenera u&#x17C;ywaj&#x105;c <code>localhost:3306</code> nie zadzia&#x142;a. Zamiast tego mo&#x17C;emy u&#x17C;y&#x107; specjalnej nazwy domenowej <code>host.docker.internal:3306</code>.</p><h3 id="dockerignore">.dockerignore</h3><p>Plik <code>.dockerignore</code> pe&#x142;ni podobn&#x105; rol&#x119; co <code>.gitignore</code> w systemie kontroli wersji. Umieszczamy go obok pliku <code>Dockerfile</code>. Plik ten zawiera zestaw regu&#x142;, kt&#xF3;re informuj&#x105; Dockera, kt&#xF3;re pliki i katalogi powinien wykluczy&#x107; z build kontekstu. Cz&#x119;sto mo&#x17C;emy spotka&#x107; si&#x119; z kopiowaniem do kontenera ca&#x142;ego katalogu z kodem aplikacji w nast&#x119;puj&#x105;cy spos&#xF3;b.</p><!--kg-card-begin: html--><pre><code class="language-bash">COPY . /var/www/html
</code></pre><!--kg-card-end: html--><p>W naszym repozytorium aplikacji mo&#x17C;e by&#x107; wiele r&#xF3;&#x17C;nych plik&#xF3;w, kt&#xF3;re niekoniecznie s&#x105; potrzebne w obrazie wynikowym np. pliki zawieraj&#x105;ce has&#x142;a i inne wra&#x17C;liwe dane lub pliki tymczasowe. Dzi&#x119;ki <code>.dockerignore</code> mo&#x17C;emy je wykluczy&#x107;. Dzi&#x119;ki temu nie tylko zmniejszymy rozmiar obrazu ale r&#xF3;wnie&#x17C; przy&#x15B;pieszymy jego budowanie ze wzgl&#x119;du na <code>cache invalidation</code>. Ka&#x17C;de polecenie <code>COPY</code> czy te&#x17C; <code>ADD</code> tworzy now&#x105; warstw&#x119; obrazu. Je&#x17C;eli pliki, w kt&#xF3;rego kontek&#x15B;cie <code>COPY</code> jest wykonywane uleg&#x142;y modyfikacji co cz&#x119;sto si&#x119; zdarza np. pliki z logami, warstwa b&#x119;dzie musia&#x142;a zosta&#x107; przebudowana. Wyd&#x142;u&#x17C;y to proces budowania obrazu dlatego warto wykluczy&#x107; tego typu pliki w <code>.dockerignore</code>. Poni&#x17C;ej przyk&#x142;adowy plik <code>.dockerignore</code>.</p><!--kg-card-begin: html--><pre><code class="language-bash line-numbers">.git
.cache
*.md
**/*.class
passwords.txt
logs/
</code></pre><!--kg-card-end: html--><h3 id="dobre-praktyki-podczas-u%C5%BCywania-dockera">Dobre praktyki podczas u&#x17C;ywania Dockera<br></h3><p><strong>Nie u&#x17C;ywaj tagu <em>latest </em>w &#x15B;rodowisku produkcyjnym</strong><br>Tag <em>latest</em> jest wygodny w u&#x17C;ytkowaniu jednak nie sprawdza si&#x119; w d&#x142;u&#x17C;szym horyzoncie czasowym. Gdy korzystasz z tagu <em>latest</em> Twoje wdro&#x17C;enia nie s&#x105; powtarzalne poniewa&#x17C; wersja obrazu jest dynamiczna i mo&#x17C;e si&#x119; zmienia&#x107;. Mo&#x17C;e to &#x142;atwo doprowadzi&#x107; do sytuacji gdy nie masz tej samej wersji aplikacji uruchomionej na wszystkich serwerach.</p><p><strong>U&#x17C;ywaj zmiennych &#x15B;rodowiskowych w celu konfiguracji aplikacji</strong><br>Dzi&#x119;ki umieszczeniu informacji konfiguracyjnych poza repozytorium kodu &#x17A;r&#xF3;d&#x142;owego bardzo proste jest uruchamianie dok&#x142;adnie tego samego kontenera w r&#xF3;&#x17C;nych &#x15B;rodowiskach bez wprowadzania zmian w repozytorium a co istotne w repozytorium nie zostaj&#x105; zapisane dane wra&#x17C;liwe. Co wa&#x17C;niejsze pozwala to na dok&#x142;adniejsze przetestowanie kontener&#xF3;w przed wdro&#x17C;eniem ich na &#x15B;rodowisko produkcyjne poniewa&#x17C; ten sam obraz jest u&#x17C;ywany we wszystkich &#x15B;rodowiskach.</p><p><strong>Unikaj przechowywania stanu w kontenerach</strong><br>Aplikacje zamykane w kontenerach powinny by&#x107; traktowane jako efemeryczne tzn. mog&#x105; &#x142;atwo si&#x119; pojawia&#x107;, restartowa&#x107; lub znika&#x107;. Je&#x17C;eli taki kontener zapisuje stan do swojego systemu plik&#xF3;w np. sesj&#x119; http proces aktualizacji aplikacji do nowej wersji spowoduje usuni&#x119;cie wszystkich danych z lokalnego stanu aplikacji. Dlatego je&#x15B;li to mo&#x17C;liwe preferowane jest pisanie aplikacji, kt&#xF3;re nie musz&#x105; przechowywa&#x107; informacji o stanie d&#x142;u&#x17C;ej ni&#x17C; przez czas potrzebny na przetworzenie pojedynczego zapytania i udzielenie odpowiedzi. Dzi&#x119;ki temu zatrzymanie kt&#xF3;regokolwiek kontenera aplikacji w minimalny spos&#xF3;b wp&#x142;ywa na jego dzia&#x142;anie. Gdy trzeba zachowa&#x107; informacje na temat stanu najlepszym podej&#x15B;ciem jest u&#x17C;ycie zewn&#x119;trznego magazynu danych np. Redis, PostgreSQL itp.</p><p><strong>Nie u&#x17C;ywaj u&#x17C;ytkownika <em>root</em></strong><br>Nie jest dobry pomys&#x142;em uruchamianie procesu kontenera z <code>UID=0</code>. W takiej sytuacji ka&#x17C;da luka bezpiecze&#x144;stwa pozwalaj&#x105;ca na omini&#x119;cie ograniczenia przestrzeni nazw (mechanizm izolacji, kt&#xF3;ry jest wykorzystywany przez <em>dockera</em>) spowoduje, &#x17C;e w systemie b&#x119;dzie dzia&#x142;a&#x142; proces ze wszystkimi uprawnieniami. Aby uruchomi&#x107; ca&#x142;y kontener z innymi uprawnieniami ni&#x17C; <em>root </em>mo&#x17C;esz skorzysta&#x107; z prze&#x142;&#x105;cznika <code>-u</code> np. <code>docker run -d -u 500 my-java-app:1.0</code>.</p>]]></content:encoded></item><item><title><![CDATA[Jak wyświetlić albumy z google photos na swojej stronie internetowej]]></title><description><![CDATA[<p>Z tego artyku&#x142;u dowiesz si&#x119; jak w prosty spos&#xF3;b bez kodowania mo&#x17C;esz doda&#x107; na swoj&#x105; stron&#x119; w miar&#x119; przyzwoity album z zdj&#x119;ciami. Efekt ko&#x144;cowy mo&#x17C;esz zobaczy&#x107; <a href="https://lsdev.pl/gallery">tutaj</a>. Na ko&#x144;cu artyku&</p>]]></description><link>https://lsdev.pl/posts/jak-wyswietlic-albumy-z-google-photos-na-swojej-stronie/</link><guid isPermaLink="false">6273fd6b15b2669d12cdd352</guid><category><![CDATA[Programming]]></category><category><![CDATA[Design]]></category><category><![CDATA[Javascript]]></category><dc:creator><![CDATA[Łukasz]]></dc:creator><pubDate>Thu, 05 May 2022 18:26:59 GMT</pubDate><media:content url="https://lsdev.pl/content/images/2022/05/photo-1519761943942-070c6bdcd395.webp" medium="image"/><content:encoded><![CDATA[<img src="https://lsdev.pl/content/images/2022/05/photo-1519761943942-070c6bdcd395.webp" alt="Jak wy&#x15B;wietli&#x107; albumy z google photos na swojej stronie internetowej"><p>Z tego artyku&#x142;u dowiesz si&#x119; jak w prosty spos&#xF3;b bez kodowania mo&#x17C;esz doda&#x107; na swoj&#x105; stron&#x119; w miar&#x119; przyzwoity album z zdj&#x119;ciami. Efekt ko&#x144;cowy mo&#x17C;esz zobaczy&#x107; <a href="https://lsdev.pl/gallery">tutaj</a>. Na ko&#x144;cu artyku&#x142;u znajdziesz linka do githuba z prostym plikiem html, kt&#xF3;ry mo&#x17C;esz wykorzysta&#x107; na swojej stronie.</p><p>Jaki&#x15B; czas temu pomy&#x15B;la&#x142;em, &#x17C;e fajnie by&#x142;oby mie&#x107; na stronie galeri&#x119; w postaci album&#xF3;w z zdj&#x119;ciami w dobrej jako&#x15B;ci, do kt&#xF3;rych mia&#x142;bym &#x142;atwy dost&#x119;p z dowolnego urz&#x105;dzenia w postaci prostego linka, kt&#xF3;ry m&#xF3;g&#x142;bym wpisa&#x107; w pasku przegl&#x105;darki lub podes&#x142;a&#x107; go komu&#x15B; innemu. Jednym zdaniem m&#xF3;wi&#x105;c wszystko w jednym miejscu.</p><p>Zacz&#x105;&#x142;em zastanawia&#x107; si&#x119; jakich technologii m&#xF3;g&#x142;bym u&#x17C;y&#x107; aby zrealizowa&#x107; to co przysz&#x142;o mi do g&#x142;owy. Postanowi&#x142;em, &#x17C;e rozwi&#x105;zanie ma by&#x107; relatywnie jak najprostsze bez kodowania po stronie serwera. Chcia&#x142;em r&#xF3;wnie&#x17C; unikn&#x105;&#x107; serwowania zdj&#x119;&#x107; z serwera, na kt&#xF3;rym dzia&#x142;a moja strona aby odci&#x105;&#x17C;y&#x107; serwer i zaoszcz&#x119;dzi&#x107; miejsce a tym samym unikn&#x105;&#x107; zb&#x119;dnych koszt&#xF3;w. Poza tym nie od dzi&#x15B; wiemy, &#x17C;e statyczny kontent lepiej serwowa&#x107; z oddzielnej domeny (<a href="https://blog.leaseweb.com/2014/06/05/need-cookie-less-domain/?ref=lsdev.pl">cookie-less-domain</a>).</p><p>Postanowi&#x142;em, &#x17C;e skorzystam z gotowych rozwi&#x105;za&#x144; w chmurze tj. <a href="https://www.dropbox.com/?ref=lsdev.pl">Dropbox</a> lub <a href="https://photos.google.com/?ref=lsdev.pl">Google Photos</a>. Natomiast pojawi&#x142; si&#x119; problem z tymi us&#x142;ugami gdy&#x17C; nie udost&#x119;pniaj&#x105; one mo&#x17C;liwo&#x15B;ci wyeksportowania kodu i zamieszczenia tre&#x15B;ci na stronie w postaci fajnie wygl&#x105;daj&#x105;cej galerii zdj&#x119;&#x107;, przynajmniej nie w takiej postaci w jakiej bym oczekiwa&#x142;. Kolejna rzecz to API tych us&#x142;ug wymaga uwierzytelniania, mimo &#x17C;e chcia&#x142;em pobiera&#x107; informacje tylko o publicznie dost&#x119;pnych albumach/folderach. Wi&#x105;za&#x142;oby si&#x119; to z zaimplementowaniem autoryzacji po stronie serwera czego chcia&#x142;em unikn&#x105;&#x107;. Zale&#x17C;a&#x142;o mi na prostocie rozwi&#x105;zania.</p><p>Ostatecznie pad&#x142;o na google photos gdy&#x17C; znalaz&#x142;em ciekawe <a href="https://www.publicalbum.org/blog/embedding-google-photos-albums?ref=lsdev.pl">narz&#x119;dzie</a>, kt&#xF3;re po podaniu linka do publicznego albumu z google photos &quot;eksportuje&quot; nam ten album i generuje gotowy kod HTML do umieszczenia na stronie. Przyk&#x142;adowy kod wygenerowany przez wspomniane narz&#x119;dzie:</p><!--kg-card-begin: html--><pre><code class="language-markup line-numbers">&lt;script src=&quot;https://cdn.jsdelivr.net/npm/publicalbum@latest/embed-ui.min.js&quot; async&gt;&lt;/script&gt;
&lt;div class=&quot;pa-gallery-player-widget&quot; style=&quot;width:100%; height:480px; display:none;&quot;
  data-link=&quot;https://photos.app.goo.gl/CSV7NDstShTUwUZq5&quot;
  data-title=&quot;Mr. Monstro&quot;
  data-description=&quot;4 new items &#xB7; Album by Pavel M.&quot;&gt;
  &lt;object data=&quot;https://lh3.googleusercontent.com/XlH6wo2PzrAEqmplYrZwV0fI-2TafTT6BRwZhKDfZSHd_zT7HIdPyPWd3Xuqhn1QQADuTJ32QFmcgYiTOEU0sC4Bvf-VyTIiq-DxxEaxIeWDYyUK_VjaW8-zrMGBvekDZT77lpduYQ=w1920-h1080&quot;&gt;&lt;/object&gt;
  &lt;object data=&quot;https://lh3.googleusercontent.com/VvK__Vx8kpPTP57WZPLblacZbTE0NqWeIGTyHSQ8Rq9pvOpWQG_CQE_tOc6jHPtj02XIBYa0Zo9fWbXXQyNYs9hDGGj34QibKFJky4W9nYBpSb57OwxiQoDyo25vzIXMTN2SNxuzqg=w1920-h1080&quot;&gt;&lt;/object&gt;
  &lt;object data=&quot;https://lh3.googleusercontent.com/HISe-DV_b4gjLvSEGzrJlsqBU2rSE8uQpSqHHKTPihg_Ax9VtfCrOrvdXF01raBeBleAWQKI7Hfb4_w9vZeJKFymQfNTlubwXxTBTbqGTPwjg7S0CBtQsQJqsspvIhD9c-pniSZrEw=w1920-h1080&quot;&gt;&lt;/object&gt;
  &lt;object data=&quot;https://lh3.googleusercontent.com/05lhR1IAQY_B9rdQ_GvHDNLe1lJsSPyyuDeIMkt--gDDAnO2_EATwif7-sfNd2K_48RvyqKmN-u2svKZ06yfh8bnrbQ5kBUrIHfZvWheTzDGhIeFd1roPor-F_BycJmVKbQO6a9EaA=w1920-h1080&quot;&gt;&lt;/object&gt;
&lt;/div&gt;
</code></pre><!--kg-card-end: html--><p>Jednak to narz&#x119;dzie nie do ko&#x144;ca spe&#x142;ni&#x142;o moje oczekiwania. Wygenerowany widget, kt&#xF3;ry wy&#x15B;wietla zdj&#x119;cia nie do ko&#x144;ca trafia w moje gusta. Kolejna rzecz to adresy do zdj&#x119;&#x107; s&#x105; na sztywno zahardkodowane w kodzie. W przypadku aktualizacji albumu w google photos nowe zdj&#x119;cia nie b&#x119;d&#x105; widoczne na stronie. Musia&#x142;bym jeszcze raz wygenerowa&#x107; kod z wykorzystaniem tego narz&#x119;dzia i zaktualizowa&#x107; stron&#x119; co by&#x142;oby uci&#x105;&#x17C;liwe. Chcia&#x142;em osi&#x105;gn&#x105;&#x107; efekt, w kt&#xF3;rym dodaj&#x105;c zdj&#x119;cia do albumu na google photos automatycznie b&#x119;d&#x105; widoczne na stronie.</p><p>W zwi&#x105;zku z tym postanowi&#x142;em zautomatyzowa&#x107; to narz&#x119;dzie. Na pocz&#x105;tku odpali&#x142;em <a href="https://portswigger.net/burp?ref=lsdev.pl">Burpa</a> aby sprawdzi&#x107; jaki endpoint jest wykorzystywany pod spodem. Okaza&#x142;o si&#x119;, &#x17C;e jest wywo&#x142;ywany nast&#x119;puj&#x105;cy endpoint:</p><!--kg-card-begin: html--><pre><code class="language-bash">POST https://www.publicalbum.org/api/v2/webapp/embed-player/jsonrpc
</code></pre><!--kg-card-end: html--><p>W ciele &#x17C;&#x105;dania jest m.in. link do publicznego albumu, kt&#xF3;ry poda&#x142;em narz&#x119;dziu. W odpowiedzi otrzymuj&#x119; m.in. takie dane jak nazwa albumu jak r&#xF3;wnie&#x17C; list&#x119; link&#xF3;w do zdj&#x119;&#x107; w tym albumie. Zak&#x142;adam, &#x17C;e pod spodem narz&#x119;dzie wykorzystuje metod&#x119; <em>web scrappingu </em>(je&#x17C;eli masz pomys&#x142; jak to mo&#x17C;e by&#x107; zrealizowane daj zna&#x107; w komentarzu).</p><p>Postanowi&#x142;em wi&#x119;c skorzysta&#x107; z tego API i wywo&#x142;ywa&#x107; je dla ka&#x17C;dego publicznego albumu, kt&#xF3;ry chc&#x119; wy&#x15B;wietli&#x107; na stronie za ka&#x17C;dym razem gdy galeria zdj&#x119;&#x107; jest &#x142;adowana. Dzi&#x119;ki temu zdj&#x119;cia wy&#x15B;wietlane na stronie b&#x119;d&#x105; zawsze &quot;zsynchronizowane&quot; z tymi w chmurze. Poni&#x17C;ej kod, kt&#xF3;ry napisa&#x142;em aby pobra&#x107; dane i wyrenderowa&#x107; galeri&#x119;:</p><!--kg-card-begin: html--><pre><code class="language-js line-numbers">function fetchAlbum(albumId) {
    const payload = {
        method: &apos;getGooglePhotosAlbum&apos;,
        params: {
            sharedLink: GOOGLE_PHOTOS_URL + &apos;/&apos; + albumId,
            imageWidth: IMAGE_WIDTH,
            imageHeight: IMAGE_HEIGHT,
            includeThumbnails: true,
            videoQuality: &apos;1080p&apos;,
            attachMetadata: false
        },
        id: 1
    };
    return fetch(&apos;/google-photo-album&apos;, {
        method: &apos;POST&apos;,
        body: JSON.stringify(payload),
        headers: {
            &apos;Content-Type&apos;: &apos;text/plain;charset=UTF-8&apos;,
            &apos;Accept&apos;: &apos;application/json&apos;
        }
    })
        .then(response =&gt; response.json())
        .then(response =&gt; response[&quot;result&quot;]);
}

$(document).ready(function () {
    (async () =&gt; {
        const items = [];
        for (const albumId of GOOGLE_ALBUM_IDS) {
            const album = await fetchAlbum(albumId);
            const title = album.title;
            album.mediaItems.forEach((mediaItem, i) =&gt; {
                if (i === 0) {
                    const item = {
                        ID: albumId,
                        src: mediaItem.url,
                        srct: mediaItem.url.split(&apos;=&apos;)[0] + `=w${THUMBNAIL_L1_WIDTH}-h${THUMBNAIL_L1_HEIGHT}`,
                        kind: &apos;album&apos;,
                        title: title
                    };
                    items.push(item);
                }
                const item = {
                    ID: mediaItem.id,
                    albumID: albumId,
                    src: mediaItem.url,
                    srct: mediaItem.url.split(&apos;=&apos;)[0] + `=w${THUMBNAIL_WIDTH}-h${THUMBNAIL_HEIGHT}`
                };
                items.push(item);
            });
        }
        return items;
    })().then(items =&gt; renderGallery(items));
</code></pre><!--kg-card-end: html--><p>Sta&#x142;a <em>GOOGLE_ALBUM_IDS </em>jest tablic&#x105;, kt&#xF3;ra przechowuje identyfikatory publicznych album&#xF3;w z google photos. Je&#x17C;eli chcesz wy&#x15B;wietli&#x107; swoje albumy po prostu wrzu&#x107; do tablicy odpowiednie identyfikatory.</p><p>Endpoint <em>/google-photo-album</em> jest us&#x142;ug&#x105; wystawion&#x105; przeze mnie, kt&#xF3;ra tylko przekierowuje do docelowego API przy okazji podmieniaj&#x105;c nag&#x142;&#xF3;wek <em>Referer</em>. Poni&#x17C;ej konfiguracja nginx:</p><!--kg-card-begin: html--><pre><code class="language-bash line-numbers">location /google-photo-album {
        proxy_set_header        Referer https://www.publicalbum.org/blog/embedding-google-photos-albums;
        proxy_pass              https://www.publicalbum.org/api/v2/webapp/embed-player/jsonrpc;
}
</code></pre><!--kg-card-end: html--><p>Za renderowanie galerii odpowiedzialna jest biblioteka <a href="https://nanogallery2.nanostudio.org/?ref=lsdev.pl">nanoGallery2</a>, kt&#xF3;ra spe&#x142;nia moje oczekiwania. Udost&#x119;pnia ciekawy, nowoczesny i interaktywny styl i dobrze sprawdza si&#x119; na urz&#x105;dzeniach mobilnych.</p><p>Zalet&#x105; rozwi&#x105;zania, kt&#xF3;re opisa&#x142;em powy&#x17C;ej jest prostota. Wystarczy jeden plik HTML aby wy&#x15B;wietli&#x107; galeri&#x119; w postaci album&#xF3;w &quot;zsynchronizowanych&quot; z google photos dzi&#x119;ki pobieraniu danych z API przy ka&#x17C;dym &#x142;adowaniu.</p><p>Na koniec warto wspomnie&#x107; o tym, &#x17C;e rozwi&#x105;zanie nie jest idealne. Pierwsz&#x105; wad&#x105; rozwi&#x105;zania jest to, &#x17C;e jest uzale&#x17C;nione od zewn&#x119;trznego API. Je&#x17C;eli API si&#x119; zmieni lub us&#x142;uga zostanie wy&#x142;&#x105;czona galeria si&#x119; nie wy&#x15B;wietli. Kolejna wada (moim zdaniem bardziej istotna) to czas &#x142;adowania galerii. Im wi&#x119;cej album&#xF3;w chcemy wy&#x15B;wietli&#x107; tym d&#x142;u&#x17C;ej b&#x119;dziemy czeka&#x107; poniewa&#x17C; ka&#x17C;dy album to oddzielne &#x17C;&#x105;danie HTTP. Je&#x17C;eli masz pomys&#x142; jak to zoptymalizowa&#x107; lub lepiej zintegrowa&#x107;/zrealizowa&#x107; daj zna&#x107; w komentarzu &#x1F609;.</p><p>Je&#x17C;eli ten artyku&#x142; by&#x142; dla Ciebie warto&#x15B;ciowy podziel si&#x119; nim z innymi. A tymczasem zostawiam Ciebie z przydatnymi linkami i utworem, kt&#xF3;rego teraz s&#x142;ucham &#x1F60E;.</p><h3 id="linki">Linki</h3><p>Galeria: <a href="https://lsdev.pl/gallery/">https://lsdev.pl/gallery/</a><br>Github: <a href="https://github.com/lukascode/photo-gallery/?ref=lsdev.pl">https://github.com/lukascode/photo-gallery/</a></p><!--kg-card-begin: html--><iframe width="100%" height="300" scrolling="no" frameborder="no" allow="autoplay" src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/28256655&amp;color=%23ff5500&amp;auto_play=false&amp;hide_related=false&amp;show_comments=true&amp;show_user=true&amp;show_reposts=false&amp;show_teaser=true&amp;visual=true"></iframe><div style="font-size: 10px; color: #cccccc;line-break: anywhere;word-break: normal;overflow: hidden;white-space: nowrap;text-overflow: ellipsis; font-family: Interstate,Lucida Grande,Lucida Sans Unicode,Lucida Sans,Garuda,Verdana,Tahoma,sans-serif;font-weight: 100;"><a href="https://soundcloud.com/eelke-kleijn?ref=lsdev.pl" title="Eelke Kleijn" target="_blank" style="color: #cccccc; text-decoration: none;">Eelke Kleijn</a> &#xB7; <a href="https://soundcloud.com/eelke-kleijn/levensgenieter?ref=lsdev.pl" title="Levensgenieter" target="_blank" style="color: #cccccc; text-decoration: none;">Levensgenieter</a></div><!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[Prosty sposób na wyeksportowanie filtrów w Thunderbird]]></title><description><![CDATA[<p>Thunderbird jest jak dot&#x105;d moim ulubionym klientem pocztowym. Dzisiaj mia&#x142;em okazj&#x119; konfigurowa&#x107; tego klienta na nowym komputerze. O ile protok&#xF3;&#x142; <a href="https://en.wikipedia.org/wiki/Internet_Message_Access_Protocol?ref=lsdev.pl">IMAP</a> zapewnia nam synchronizacj&#x119; poczty w tym prywatnych folder&#xF3;w z poziomu wielu urz&#x105;dze&#x144; o tyle nie</p>]]></description><link>https://lsdev.pl/posts/prosty-sposob-na-wyeksportowanie-filtrow-w-thunderbird/</link><guid isPermaLink="false">6269cca990f1fe03849b798b</guid><category><![CDATA[Software]]></category><category><![CDATA[Thunderbird]]></category><dc:creator><![CDATA[Łukasz]]></dc:creator><pubDate>Thu, 28 Apr 2022 01:10:45 GMT</pubDate><media:content url="https://lsdev.pl/content/images/2022/04/content-pixie-PSie8BkuB0w-unsplash-1.webp" medium="image"/><content:encoded><![CDATA[<img src="https://lsdev.pl/content/images/2022/04/content-pixie-PSie8BkuB0w-unsplash-1.webp" alt="Prosty spos&#xF3;b na wyeksportowanie filtr&#xF3;w w Thunderbird"><p>Thunderbird jest jak dot&#x105;d moim ulubionym klientem pocztowym. Dzisiaj mia&#x142;em okazj&#x119; konfigurowa&#x107; tego klienta na nowym komputerze. O ile protok&#xF3;&#x142; <a href="https://en.wikipedia.org/wiki/Internet_Message_Access_Protocol?ref=lsdev.pl">IMAP</a> zapewnia nam synchronizacj&#x119; poczty w tym prywatnych folder&#xF3;w z poziomu wielu urz&#x105;dze&#x144; o tyle nie dotyczy to filtr&#xF3;w czyli pewnych regu&#x142; i akcji im towarzysz&#x105;cych, kt&#xF3;re s&#x105; specyficzne dla konkretnego klienta.</p><p>Z uwagi na to, &#x17C;e mam skonfigurowanych do&#x15B;&#x107; sporo filtr&#xF3;w nie chcia&#x142;o mi si&#x119; ich<br>ponownie r&#x119;cznie konfigurowa&#x107;.</p><p>Poni&#x17C;ej wyja&#x15B;ni&#x119; jak w prosty spos&#xF3;b mo&#x17C;na je wyeksportowa&#x107; (przenie&#x15B;&#x107;).<br>W moim przypadku &quot;przenosi&#x142;em si&#x119;&quot; z Linuksa na Maca.</p><p>Pierwszym krokiem jest odnalezienie lokalizacji plik&#xF3;w, w kt&#xF3;rych<br>Thunderbird przechowuje dane u&#x17C;ytkownika tj. ustawienia, wiadomo&#x15B;ci<br>w tym tak&#x17C;e filtry. Lokalizacja mo&#x17C;e si&#x119; r&#xF3;&#x17C;ni&#x107; w zale&#x17C;no&#x15B;ci od<br>systemu operacyjnego.</p><p>W przypadku Linuksa dane sk&#x142;adowane s&#x105; w katalogu domowym<br>pod &#x15B;cie&#x17C;k&#x105; <em>~/.thunderbird</em>. W przypadku Maca powinna by&#x107; to nast&#x119;puj&#x105;ca<br>lokalizacja: <em>~/Library/Thunderbird</em>. W obu przypadkach <em>~</em> wskazuje<br>na katalog domowy aktualnie zalogowanego u&#x17C;ytkownika.</p><p>Thunderbird dla ka&#x17C;dego konta przechowuj&#x119; filtry w pliku o nazwie<br><em>msgFilterRules.dat</em>. Wyszukam zatem na Linuksie pliku z filtrami, kt&#xF3;ry mnie interesuje tj. ten, kt&#xF3;ry zamierzam przenie&#x15B;&#x107; na Maca gdzie skonfigurowa&#x142;em &#x15B;wie&#x17C;o zainstalowanego klienta pocztowego tak aby nie konfigurowa&#x107; filtr&#xF3;w r&#x119;cznie.</p><!--kg-card-begin: html--><pre><code class="language-bash">$ find ~/.thunderbird/ -name msgFilterRules.dat

/home/lukasz/.thunderbird/npo5t3or.default-release/Mail/Local Folders/msgFilterRules.dat
/home/lukasz/.thunderbird/npo5t3or.default-release/ImapMail/pro2.mail.ovh.net/msgFilterRules.dat
/home/lukasz/.thunderbird/npo5t3or.default-release/ImapMail/imap.gmail.com/msgFilterRules.dat
</code></pre><!--kg-card-end: html--><p>W tym przypadku interesuje mnie konto na gmail w zwi&#x105;zku z tym kopiuj&#x119; plik<br>z lokalizacji <em>.../ImapMail/imap.gmail.com/msgFilterRules.dat</em> i przerzucam na Maca.</p><p>Na Macu szukam analogicznej &#x15B;cie&#x17C;ki.</p><!--kg-card-begin: html--><pre><code class="language-bash">find ~/Library/Thunderbird -iname msgFilterRules.dat

/Users/lukas/Library/Thunderbird/Profiles/okd5md7p.default-release/Mail/Local Folders/msgFilterRules.dat
/Users/lukas/Library/Thunderbird/Profiles/okd5md7p.default-release/ImapMail/imap.gmail.com/msgFilterRules.dat
</code></pre><!--kg-card-end: html--><p>Nast&#x119;pnie kopiuj&#x119; pobrany wcze&#x15B;niej plik do odpowiedniej lokalizacji nadpisuj&#x105;c ten domy&#x15B;lny.</p><!--kg-card-begin: html--><pre><code class="language-bash">cp ~/Downloads/msgFilterRules.dat ./Profiles/okd5md7p.default-release/ImapMail/imap.gmail.com/</code></pre><!--kg-card-end: html--><p>Nast&#x119;pnie po zrestartowaniu Thunderbird filtry powinny zosta&#x107; poprawnie dodane i widoczne w mened&#x17C;erze filtr&#xF3;w.</p>]]></content:encoded></item><item><title><![CDATA[Java interview, pytania rekrutacyjne]]></title><description><![CDATA[<p><br>Przez ostatnie miesi&#x105;ce bra&#x142;em udzia&#x142; w wielu rozmowach rekrutacyjnych na stanowisko<br><em>Java Software Developera</em>. Wiele z pyta&#x144;, kt&#xF3;re si&#x119; pojawi&#x142;y lub pojawia&#x142;y najcz&#x119;&#x15B;ciej, uda&#x142;o mi si&#x119; zapami&#x119;ta&#x107; i</p>]]></description><link>https://lsdev.pl/posts/java-interview-pytania-rekrutacyjne/</link><guid isPermaLink="false">61d70fce39226d9a0f928ae7</guid><category><![CDATA[Interview]]></category><category><![CDATA[Java]]></category><category><![CDATA[Programming]]></category><dc:creator><![CDATA[Łukasz]]></dc:creator><pubDate>Wed, 19 Jan 2022 01:20:02 GMT</pubDate><media:content url="https://lsdev.pl/content/images/2022/01/interview.webp" medium="image"/><content:encoded><![CDATA[<img src="https://lsdev.pl/content/images/2022/01/interview.webp" alt="Java interview, pytania rekrutacyjne"><p><br>Przez ostatnie miesi&#x105;ce bra&#x142;em udzia&#x142; w wielu rozmowach rekrutacyjnych na stanowisko<br><em>Java Software Developera</em>. Wiele z pyta&#x144;, kt&#xF3;re si&#x119; pojawi&#x142;y lub pojawia&#x142;y najcz&#x119;&#x15B;ciej, uda&#x142;o mi si&#x119; zapami&#x119;ta&#x107; i zapisa&#x107;. Dzisiaj chcia&#x142;bym podzieli&#x107; si&#x119; z Tob&#x105; tymi pytaniami.<br>Je&#x17C;eli w&#x142;a&#x15B;nie przygotowujesz si&#x119; do rozm&#xF3;w na podobne stanowisko mam nadzi&#x119;j&#x119;, &#x17C;e<br>poni&#x17C;szy zestaw pyta&#x144; b&#x119;dzie dla Ciebie warto&#x15B;ciowy a odpowiedzi, kt&#xF3;re przygotowa&#x142;em w jakim&#x15B; stopniu uzupe&#x142;ni&#x105; Twoj&#x105; wiedz&#x119;.</p><div class="kg-card kg-toggle-card" data-kg-toggle-state="close"><div class="kg-toggle-heading"><h4 class="kg-toggle-heading-text">1. W jakich projektach uczestniczy&#x142;e&#x15B;? Jakich technologii u&#x17C;ywa&#x142;e&#x15B;?</h4><button class="kg-toggle-card-icon"><svg id="Regular" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path class="cls-1" d="M23.25,7.311,12.53,18.03a.749.749,0,0,1-1.06,0L.75,7.311"/></svg></button></div><div class="kg-toggle-content"><p>Standardowe pytanie. Tutaj pozostawiam pole do popisu Tobie :)</p></div></div><div class="kg-card kg-toggle-card" data-kg-toggle-state="close"><div class="kg-toggle-heading"><h4 class="kg-toggle-heading-text">2. Jak dzia&#x142;a JIT? Czy ma jakie&#x15B; wady?</h4><button class="kg-toggle-card-icon"><svg id="Regular" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path class="cls-1" d="M23.25,7.311,12.53,18.03a.749.749,0,0,1-1.06,0L.75,7.311"/></svg></button></div><div class="kg-toggle-content"><p>Program napisany z wykorzystaniem j&#x119;zyka Java jest kompilowany do kodu bajtowego, kt&#xF3;ry jest zrozumia&#x142;y dla maszyny wirtualnej Javy. JVM wykonuj&#x105;c program interpretuje kolejne instrukcje kodu bajtowego konwertuj&#x105;c je na natywne instrukcje maszyny, na kt&#xF3;rej jest uruchomiona. Poniewa&#x17C; interpretacja jest wolna w por&#xF3;wnaniu do wykonywania kodu natywnego tw&#xF3;rcy wprowadzili do &#x15B;rodowiska uruchomieniowego JITa.</p><p>JIT (<em>Just-In-Time compiler</em>) jest komponentem maszyny wirtualnej Javy, kt&#xF3;ry poprawia wydajno&#x15B;&#x107; aplikacji stosuj&#x105;c r&#xF3;&#x17C;nego rodzaju optymalizacje np. null check elimination, branch prediction, zagnie&#x17C;d&#x17C;anie metod (<em>method inlining</em>), eliminacja martwego kodu (<em>dead code elimination</em>), kompilacja do kodu natywnego i wiele innych.</p><p>Dla ka&#x17C;dej z metod JVM zlicza ilo&#x15B;&#x107; jej wywo&#x142;a&#x144;. Kiedy ilo&#x15B;&#x107; wywo&#x142;a&#x144; danej metody osi&#x105;gnie pewien pr&#xF3;g (domy&#x15B;lnie 2000) metoda a w zasadzie jej bytecode jest przekazywany do kompilatora JIT, kt&#xF3;ry jest odpowiedzialny za wykonanie na niej zestawu faz/optymalizacji, o kt&#xF3;rych wspomnia&#x142;em wcze&#x15B;niej w tym na ko&#x144;cu kompilacji do kodu natywnego. Skompilowany kod natywny jest zapisywany w przestrzeni procesu JVM w tzw. <em>code cache</em>.</p><p>JIT po starcie aplikacji zaczyna j&#x105; profilowa&#x107; analizuj&#x105;c nasz kod i zbieraj&#x105;c r&#xF3;&#x17C;nego rodzaju statystyki. Ma to wp&#x142;yw na wydajno&#x15B;&#x107; naszej aplikacji gdy&#x17C; JVM przeznacza sporo zasob&#xF3;w na ten proces szczeg&#xF3;lnie na pocz&#x105;tku gdy aplikacja dzia&#x142;a w trybie w pe&#x142;ni interpretowanym bez &#x17C;adnych optymalizacji co mo&#x17C;e by&#x107; jedn&#x105; z wad JIT&apos;a. Proces po starcie nazywamy &quot;rozgrzewaniem&quot; (<em>warm up</em>). Warto wzi&#x105;&#x107; ten proces pod uwag&#x119; gdy badamy nasz&#x105; aplikacj&#x119; pod k&#x105;tem wydajno&#x15B;ciowym i pami&#x119;ta&#x107;, &#x17C;e wyniki mog&#x105; si&#x119; r&#xF3;&#x17C;ni&#x107; od tych, kt&#xF3;re zaobserwujemy na &#x15B;rodowisku produkcyjnym.</p></div></div><div class="kg-card kg-toggle-card" data-kg-toggle-state="close"><div class="kg-toggle-heading"><h4 class="kg-toggle-heading-text">3. Do czego s&#x142;u&#x17C;y adnotacja @PostConstruct?</h4><button class="kg-toggle-card-icon"><svg id="Regular" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path class="cls-1" d="M23.25,7.311,12.53,18.03a.749.749,0,0,1-1.06,0L.75,7.311"/></svg></button></div><div class="kg-toggle-content"><p>Adnotacja <em>@PostConstruct</em> pochodzi z specyfikacji <a href="https://en.wikipedia.org/wiki/JSR_250?ref=lsdev.pl">JSR 250</a> (<em>Java Specification Request</em>). Spring implementuje t&#x105; specyfikacj&#x119; tym samym zapewniaj&#x105;c dzia&#x142;anie tej adnotacji.</p><p>Adnotacj&#x119; <em>@PostConstruct</em> umieszczamy nad metod&#x105; klasy, kt&#xF3;ra jest beanem tj. komponentem zarz&#x105;dzalnym przez Springa. Spring wywo&#x142;a t&#x105; metod&#x119; jako callback po poprawnym zainicjalizowaniu naszego beana tj. po poprawnym wstrzykni&#x119;ciu zale&#x17C;no&#x15B;ci dla tego komponentu. Wspomniany callback mo&#x17C;e by&#x107; dobrym miejscem na r&#xF3;&#x17C;nego rodzaju post inicjalizacj&#x119;.</p><p>Kontrakt/Specyfikacja zapewnia, &#x17C;e ta metoda zostanie wywo&#x142;ana tylko raz w trakcie cyklu &#x17C;ycia tego beana. Je&#x17C;eli chodzi o sygantur&#x119; metody powinna by&#x107; typu void, niestatyczna, bezparametrowa o dowolnym poziomie widoczno&#x15B;ci (public, protected, package private or private).</p><p>Warto zaznaczy&#x107;, &#x17C;e u&#x17C;ywanie <em>@PostConstruct</em> ma sens tylko wtedy gdy u&#x17C;ywamy wstrzykiwania typu <em>field injection</em> lub <em>setter injection</em>. Je&#x17C;eli u&#x17C;ywamy wstrzykiwania przez konstruktor czyli rekomendowanego przeze mnie podej&#x15B;cia inicjalizacj&#x119; mo&#x17C;emy wykona&#x107; w konstruktorze. Dzi&#x119;ki temu &#x142;atwiej jest przetestowa&#x107; taki komponent, kt&#xF3;ry mo&#x17C;emy stworzy&#x107; poprawnie tylko i wy&#x142;&#x105;cznie z wykorzystaniem konstruktora bez wzgl&#x119;du na to czy robimy to w testach czy w trakcie stawiania ca&#x142;ego kontekstu springowego bez bawienia si&#x119; w mechanizmy refleksji.</p><p>W praktyce o adnotacji <em>@PostConstruct</em> powinni&#x15B;my zapomnie&#x107; na rzecz wstrzykiwania przez konstruktor ewentualnie zostawiaj&#x105;c j&#x105; sobie jako alternatywe dla kodu <em>legacy</em>.</p></div></div><div class="kg-card kg-toggle-card" data-kg-toggle-state="close"><div class="kg-toggle-heading"><h4 class="kg-toggle-heading-text">4. Wymie&#x144; sposoby wstrzykiwania zale&#x17C;no&#x15B;ci. Jakiego sposobu u&#x17C;ywasz i dlaczego?</h4><button class="kg-toggle-card-icon"><svg id="Regular" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path class="cls-1" d="M23.25,7.311,12.53,18.03a.749.749,0,0,1-1.06,0L.75,7.311"/></svg></button></div><div class="kg-toggle-content"><p>Wstrzykiwanie zale&#x17C;no&#x15B;ci (<em>Dependency Injection</em>) jest wzorcem projektowym, kt&#xF3;ry polega na przekazywaniu do obiektu gotowych instacji obiekt&#xF3;w (zale&#x17C;no&#x15B;ci), z kt&#xF3;rych ten obiekt si&#x119; sk&#x142;ada tak aby zapewni&#x107; swoj&#x105; funkcjonalno&#x15B;&#x107; w przeciwie&#x144;stwie do tworzenia ich samodzielnie. Podej&#x15B;cie to ma wiele zalet m.in. obiekt, kt&#xF3;ry dostaje zale&#x17C;no&#x15B;ci z zewn&#x105;trz (np. z kontenera springowego) nie jest uzale&#x17C;niony od konkretnej implementacji a wi&#x119;c &#x142;atwo mo&#x17C;na przekonfigurowa&#x107; aplikacj&#x119; lub podmieni&#x107; implementacj&#x119; w testach.</p><p>Wyr&#xF3;&#x17C;niamy nast&#x119;puj&#x105;ce sposoby wstrzykiwania zale&#x17C;no&#x15B;ci:</p><ul><li>field injection</li><li>setter injection</li><li>constructor injection</li></ul><p>Preferuj&#x119; ostatnie podej&#x15B;cie czyli wstrzykiwanie przez konstruktor. Podej&#x15B;cie to pozwala w &#x142;atwy spos&#xF3;b na skonfigurowanie obiektu pod testy bez wykorzystywania mechanizm&#xF3;w refleksji. Mamy r&#xF3;wnie&#x17C; pewno&#x15B;&#x107;, &#x17C;e obiekt stworzony przez <em>new</em> zawsze zwr&#xF3;ci nam poprawny i sp&#xF3;jny obiekt gotowy do dzia&#x142;ania w przeciwie&#x144;stwie do <em>field injection</em> / <em>setter injection</em> gdzie &#x142;atwo jest si&#x119; &#x201E;nadzia&#x107;&#x201D; na <em>NullPointerException</em>. Konstruktor pe&#x142;ni tak&#x17C;e funkcj&#x119; dokumentacyjn&#x105; klasy gdy&#x17C; wiemy dok&#x142;adnie co jest potrzebne aby poprawnie skonfigurowa&#x107; obiekt czego nie ma np. w przypadku setter injection gdzie musieliby&#x15B;my si&#x119; zastanawia&#x107; jakich setterow wystarczy u&#x17C;y&#x107; aby obiekt by&#x142; w sp&#xF3;jnym stanie. Wad&#x105; u&#x17C;ywania setter&#xF3;w jest r&#xF3;wnie&#x17C; to, &#x17C;e &#x142;amiemy zasad&#x119; hermetyzacji przez co &#x142;atwo w niekontrolowany spos&#xF3;b popsu&#x107; stan obiektu.</p><p>Kolejnym argumentem na rzecz constructor injection jest to, &#x17C;e mamy kontrol&#x119; nad ilo&#x15B;ci&#x105; zale&#x17C;no&#x15B;ci. Przyk&#x142;adowo dodanie 5 zale&#x17C;no&#x15B;ci do konstruktora ju&#x17C; mo&#x17C;e zniech&#x119;ca&#x107; - szybko widzimy jak ich liczba si&#x119; rozrasta wi&#x119;c mo&#x17C;emy dzi&#x119;ki temu szybciej podj&#x105;&#x107; decyzj&#x119; w stron&#x119; refactoringu i stworzeniu osobnej klasy odpowiedzialnej za dan&#x105; podfunkcjonalno&#x15B;&#x107;. W przypadku <em>field injection</em> dodanie kolejnej zale&#x17C;no&#x15B;ci jest banalnie proste i przyjemne, liczba zale&#x17C;no&#x15B;ci szybko ro&#x15B;nie przez co klasa mo&#x17C;e z czasem by&#x107; trudna w utrzymaniu.</p></div></div><div class="kg-card kg-toggle-card" data-kg-toggle-state="close"><div class="kg-toggle-heading"><h4 class="kg-toggle-heading-text">5. Co to jest hermetyzacja?</h4><button class="kg-toggle-card-icon"><svg id="Regular" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path class="cls-1" d="M23.25,7.311,12.53,18.03a.749.749,0,0,1-1.06,0L.75,7.311"/></svg></button></div><div class="kg-toggle-content"><p>Hermetyzacja (lub enkapsulacja) polega na ukrywaniu implementacji czyli sk&#x142;adowych lub metod obiektu, z kt&#xF3;rych si&#x119; sk&#x142;ada. Hermetyzacja nadaje obiektowi charakter czarnej skrzynki co jest kluczowe dla koncepcji wielokrotnego u&#x17C;ycia kodu jak i jego niezawodno&#x15B;ci. Oznacza to, &#x17C;e spos&#xF3;b przechowywania danych w klasie mo&#x17C;e si&#x119; diametralnie zmieni&#x107;, ale dop&#xF3;ki udost&#x119;pnia ona te same metody do manipulacji danymi &#x17C;aden obiekt nie zostanie tym dotkni&#x119;ty. Je&#x17C;eli stan obiektu zmieni si&#x119; mimo, &#x17C;e nie wywo&#x142;ano na jego rzecz &#x17C;adnej metody oznacza to, &#x17C;e zosta&#x142;a z&#x142;amana zasada hermetyzacji.</p></div></div><div class="kg-card kg-toggle-card" data-kg-toggle-state="close"><div class="kg-toggle-heading"><h4 class="kg-toggle-heading-text">6. Jakie rzeczy wesz&#x142;y od Java 11?</h4><button class="kg-toggle-card-icon"><svg id="Regular" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path class="cls-1" d="M23.25,7.311,12.53,18.03a.749.749,0,0,1-1.06,0L.75,7.311"/></svg></button></div><div class="kg-toggle-content"><p>Pozwol&#x119; sobie wymieni&#x107; tylko niekt&#xF3;re nowe funkcjonalno&#x15B;ci. Po komplet odsy&#x142;am do <a href="https://www.baeldung.com/java-11-new-features?ref=lsdev.pl">tego</a> artyku&#x142;u.</p><ul><li>nowe metody w klasie String: <em>isBlank</em>, <em>lines</em>, <em>strip</em>, <em>stripLeading</em>, <em>stripTrailing</em>, <em>repeat</em></li><li>nowe statyczne metody w klasie Files: <em>readString</em>, <em>writeString</em></li><li>nowy wydajniejszy HTTP client z wsparciem HTTP/1.1, HTTP/1.2, Web Socket</li></ul></div></div><div class="kg-card kg-toggle-card" data-kg-toggle-state="close"><div class="kg-toggle-heading"><h4 class="kg-toggle-heading-text">7. Jak napisa&#x142;by&#x15B; metod&#x119; kontrolera do usuni&#x119;cia wielu u&#x17C;ytkownik&#xF3;w?</h4><button class="kg-toggle-card-icon"><svg id="Regular" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path class="cls-1" d="M23.25,7.311,12.53,18.03a.749.749,0,0,1-1.06,0L.75,7.311"/></svg></button></div><div class="kg-toggle-content"><p>Jednym ze sposob&#xF3;w na rozwi&#x105;zanie tego zadania mo&#x17C;e by&#x107; przekazanie listy identyfikator&#xF3;w do usuni&#x119;cia w nast&#x119;puj&#x105;cy spos&#xF3;b:</p><p><em>@DeleteMapping(&quot;/users/{userIds}&quot;)</em><br><em>public void deleteUsers(@PathVariable List&lt;Long&gt; userIds) {...}</em><br></p></div></div><div class="kg-card kg-toggle-card" data-kg-toggle-state="close"><div class="kg-toggle-heading"><h4 class="kg-toggle-heading-text">8. Mikroserwisy vs Monolity</h4><button class="kg-toggle-card-icon"><svg id="Regular" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path class="cls-1" d="M23.25,7.311,12.53,18.03a.749.749,0,0,1-1.06,0L.75,7.311"/></svg></button></div><div class="kg-toggle-content"><p>Mikroserwisy s&#x105; sposobem na projektowanie aplikacji jako niezale&#x17C;nie wdra&#x17C;anych us&#x142;ug w przeciwie&#x144;stwie do monolit&#xF3;w, kt&#xF3;rych wszystkie funkcjonalno&#x15B;ci s&#x105; wdra&#x17C;ane i uruchamiane w jednym procesie aplikacyjnym.</p><p>Pojedy&#x144;czy mikroserwis jest zazwyczaj odpowiedzialny za jak&#x105;&#x15B; pojedy&#x144;cz&#x105; funkcjonalno&#x15B;&#x107;/obszar, kt&#xF3;ry trzeba dostarczy&#x107; dla biznesu.</p><p>W monolicie realizacja konkretnego przypadku biznesowego jest wykonywana przez zbi&#xF3;r obiekt&#xF3;w/serwis&#xF3;w komunikuj&#x105;cych si&#x119; pomi&#x119;dzy sob&#x105;, &#x17C;yj&#x105;cych w obr&#x119;bie tego samego procesu. Natomiast w architekturze mikroserwisowej na jeden proces biznesowy mo&#x17C;e by&#x107; zaanga&#x17C;owanych wiele mikroserwis&#xF3;w komunikuj&#x105;cych si&#x119; pomi&#x119;dzy sob&#x105; u&#x17C;ywaj&#x105;c takich technologii jak np. HTTP REST, JMS, gRCP.</p><p>Architektura w oparciu o mikroserwisy ma wiele <strong>zalet</strong> szczeg&#xF3;lnie w przypadku du&#x17C;ych z&#x142;o&#x17C;onych system&#xF3;w m.in.:</p><ul><li><strong>skalowalno&#x15B;&#x107;</strong></li></ul><p>Poszczeg&#xF3;lne komponenty mog&#x105; by&#x107; skalowalne niezale&#x17C;nie od pozosta&#x142;ych. Jest to niesamowita zaleta. Mo&#x17C;emy mie&#x107; komponent, kt&#xF3;ry intensywnie wykorzystuje CPU np. jest odpowiedzialny za przetwarzanie obraz&#xF3;w w zwi&#x105;zku z tym wdra&#x17C;amy taki mikroserwis na instancje zoptymalizowane pod k&#x105;tem CPU np. Amazon EC2 Compute Optimized Instances. Inny modu&#x142; mo&#x17C;e by&#x107; baz&#x105; danych in-memory wi&#x119;c wdro&#x17C;ymy go na instancje zoptymalizowane pod pami&#x119;&#x107; np. Amazon EC2 Memory Optimized instances. W przypadku monolitu nie mamy takich mo&#x17C;liwo&#x15B;ci, musimy p&#xF3;j&#x15B;&#x107; na kompromis je&#x17C;eli chodzi o hardware. W miar&#x119; jak monolit ro&#x15B;nie musimy skalowa&#x107; wertykalnie czyli dok&#x142;ada&#x107; zasob&#xF3;w sprz&#x119;towych tj. CPU, RAM a je&#x17C;eli chcemy aby aplikacja obs&#x142;u&#x17C;y&#x142;a wi&#x119;kszy ruch skaluj&#x105;c horyzontalnie skalujemy ca&#x142;y du&#x17C;y monolit mimo, &#x17C;e np. cz&#x119;&#x15B;&#x107; us&#x142;ug jest wykorzystywana rzadko i tego nie wymaga co jest jednocze&#x15B;nie trudne i kosztowne.</p><ul><li><strong>niezawodno&#x15B;&#x107;</strong></li></ul><p>Kolejny problem w przypadku monolitu jest taki, &#x17C;e wszystkie modu&#x142;y naszej aplikacji s&#x105; cz&#x119;&#x15B;ci&#x105; jednego procesu aplikacji tzn, &#x17C;e problem powsta&#x142;y w jednym module np. wyciek pami&#x119;ci mo&#x17C;e wp&#x142;yn&#x105;&#x107; na dzia&#x142;anie ca&#x142;ego procesu co w efekcie mo&#x17C;e skutkowa&#x107; po&#x142;o&#x17C;eniem ca&#x142;ej aplikacji i niedost&#x119;pno&#x15B;ci&#x105; ca&#x142;ego systemu. Mikroserwisy nie maj&#x105; tego problemu ze wzgl&#x119;du na to, &#x17C;e s&#x105; to niezale&#x17C;nie wdra&#x17C;ane komponenty uruchamiane jako niezale&#x17C;ne procesy w systemie operacyjnym.</p><ul><li><strong>dowolno&#x15B;&#x107; technologii (polyglot)</strong></li></ul><p>Ka&#x17C;dy element mo&#x17C;e by&#x107; tworzony w technologii najlepiej dostosowanej do funkcjonalno&#x15B;ci jak&#x105; ma realizowa&#x107; zgodnie z &quot;use the right tool for the right job&quot; np. z wykorzystaniem technologii <em>Java</em> i <em>Spring</em> wystawimy us&#x142;ugi restowe, Pythona u&#x17C;yjemy do Machine Learning a <em>C/C++</em> do napisania aplikacji typu video-streaming.</p><ul><li><strong>modularno&#x15B;&#x107;</strong></li></ul><p>Ma&#x142;e komponenty pozwalaj&#x105; na &#x142;atwe wdro&#x17C;enie i rozbudow&#x119; bez konieczno&#x15B;ci ingerencji w ca&#x142;&#x105; aplikacj&#x119;. W monolicie jedna ma&#x142;a zmiana mog&#x142;a powodowa&#x107; redeploy ca&#x142;ego systemu, kt&#xF3;ry jak mo&#x17C;emy si&#x119; spodziewa&#x107; by&#x142; d&#x142;ugim i &#x17C;mudnym procesem budowania, wykonywania si&#x119; test&#xF3;w jednostkowych i integracyjnych oraz innych krok&#xF3;w co mog&#x142;o skutkowa&#x107; tak&#x17C;e niedost&#x119;pno&#x15B;ci&#x105; ca&#x142;ego systemu przez jaki&#x15B; czas. W mikroserwisach ma&#x142;e zmiany s&#x105; prostsze do zrealizowania i wdro&#x17C;enia. Dodatkowo &#x142;atwo podzieli&#x107; takie zmiany pomi&#x119;dzy zespo&#x142;ami developerskimi w przeciwie&#x144;stwie do monolitu gdzie praca wielu os&#xF3;b nad jednym olbrzymim komponentem jest ci&#x119;&#x17C;ka i trudna w utrzymaniu.</p><p>Jak ze wszystkim oczywi&#x15B;cie architektura w oparciu o mikroserwisy ma r&#xF3;wnie&#x17C; <strong>wady</strong> i nale&#x17C;y przemy&#x15B;le&#x107; czy b&#x119;dzie mia&#x142;a zastosowanie w naszym systemie m.in:</p><ul><li><strong>z&#x142;o&#x17C;ono&#x15B;&#x107;</strong></li></ul><p>Ze wzgl&#x119;du na to, &#x17C;e aplikacja sk&#x142;ada si&#x119; z wielu serwis&#xF3;w komunikuj&#x105;cych si&#x119; mi&#x119;dzy sob&#x105; musimy zapewni&#x107; aby komuniacja pomi&#x119;dzy nimi zachodzi&#x142;a w bezpieczny spos&#xF3;b dodatkowo dochodzi nam r&#xF3;wnie&#x17C; obs&#x142;uga r&#xF3;&#x17C;nych b&#x142;&#x119;d&#xF3;w g&#x142;&#xF3;wnie sieciowych zwi&#x105;zanych z nasz&#x105; infrastruktur&#x105;. W przypadku monolitu nie mieli&#x15B;my tego problemu poniewa&#x17C; wszystkie funkcjonalno&#x15B;ci by&#x142;y w &#x15B;rodku wi&#x119;c na wywo&#x142;anie lokalne metody nie musieli&#x15B;my si&#x119; specjalnie zabezpiecza&#x107;.</p><p>Testowanie w &#x15B;rodowisku rozproszonym, zarz&#x105;dzanie infrastruktur&#x105; r&#xF3;wnie&#x17C; jest wyzwaniem i narzutem na z&#x142;o&#x17C;ono&#x15B;&#x107;.</p><ul><li><strong>zapewnienie sp&#xF3;jno&#x15B;ci danych</strong></li></ul><p>W przypadku monolitu zapewnienie sp&#xF3;jno&#x15B;ci by&#x142;o relatywnie proste gdy&#x17C; mieli&#x15B;my lokaln&#x105; transakcj&#x119; ACID, kt&#xF3;ra zapewnia&#x142;a nam sp&#xF3;jno&#x15B;&#x107; i atomowo&#x15B;&#x107; na poziomie bazy danych. W przypadku architektury mikroserwisowej gdzie mamy do czynienia z systemem rozproszonym nie jest tak &#x142;atwo i w zale&#x17C;no&#x15B;ci od potrzeb musimy posi&#x142;kowa&#x107; si&#x119; wzorcami typu <em>Saga Pattern</em> i <em>Eventual Consistency</em>.</p></div></div><div class="kg-card kg-toggle-card" data-kg-toggle-state="close"><div class="kg-toggle-heading"><h4 class="kg-toggle-heading-text">9. Co wiesz o <em>SOLID</em>, <em>IoC</em>, <em>Dependency Injection</em>?</h4><button class="kg-toggle-card-icon"><svg id="Regular" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path class="cls-1" d="M23.25,7.311,12.53,18.03a.749.749,0,0,1-1.06,0L.75,7.311"/></svg></button></div><div class="kg-toggle-content"><p>SOLID opisuje pi&#x119;c podstawowych zasad projektowania w programowaniu obiektowym.</p><p>S - Single Responsibility Principle<br>Ka&#x17C;da klasa powinna by&#x107; skoncetrowana tylko na jednym zadaniu, mie&#x107; tylko jeden pow&#xF3;d do zmiany.</p><p>O - Open/Closed Principle<br>Klasy powinny by&#x107; otwarte na rozszerzenia i zamkni&#x119;te na modyfikacje. Przy nowych wymaganiach kod nie powinien by&#x107; modyfikowany ale dodawany nowy, kt&#xF3;ry rozszerza funkcjonalno&#x15B;&#x107;.</p><p>L - Liskov Substitution Principle<br>Klasa pochodna powinna by&#x107; mo&#x17C;liwa do u&#x17C;ycia w miejsce klasy nadrz&#x119;dnej zachowuj&#x105;c si&#x119; w taki sam spos&#xF3;b bez modyfikacji. Klasa pochodna nie ma wp&#x142;ywu na zachowanie si&#x119; klasy nadrz&#x119;dnej.</p><p>I - Interface Segregation Principle<br>Wiele dedykowanych interfejs&#xF3;w jest lepsze ni&#x17C; jeden og&#xF3;lny.</p><p>D - Dependency Inversion Principle<br>Wysokopoziomowe modu&#x142;y nie powinny zale&#x17C;e&#x107; od modu&#x142;&#xF3;w niskopoziomowych - zale&#x17C;no&#x15B;ci mi&#x119;dzy nimi powinny wynika&#x107; z abstrakcji. Kod powinien zale&#x17C;e&#x107; od rzeczy &#x201E;stabilnych&#x201D; tj. abstrakcji, kt&#xF3;re si&#x119; nie zmieniaj&#x105;.</p><p>IoC (<em>Inversion of Control</em>) jest wzrorcem architektury polegaj&#x105;cym na odwr&#xF3;ceniu sterowania programem i zrzuceniu tego na framework np. Spring. Implementacj&#x105; tego wzorca mo&#x17C;e by&#x107; np. <em>Dependency Injection</em> lub <em>Aspect Oriented Programming</em>.</p></div></div><div class="kg-card kg-toggle-card" data-kg-toggle-state="close"><div class="kg-toggle-heading"><h4 class="kg-toggle-heading-text">10. Dlaczego <em>BigDecimal</em> jest lepszy od <em>double</em> do przechowywania warto&#x15B;ci pieni&#x119;&#x17C;nych? Dlaczego <em>double</em> nie jest precyzyjny?</h4><button class="kg-toggle-card-icon"><svg id="Regular" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path class="cls-1" d="M23.25,7.311,12.53,18.03a.749.749,0,0,1-1.06,0L.75,7.311"/></svg></button></div><div class="kg-toggle-content"><p>Typ <em>double</em> nie jest precyzyjny w kontek&#x15B;cie oblicze&#x144; finansowych poniewa&#x17C; obliczenia wykonywane s&#x105; w systemie binarnym zgodnie z standardem IEEE 754 w przeciwie&#x144;stwie do <em>BigDecimal</em>, kt&#xF3;rego obliczenia dzia&#x142;aj&#x105; na systemie dziesi&#x119;tnym. W zwi&#x105;zku z tym nie jeste&#x15B;my w stanie wyrazi&#x107; dok&#x142;adnie pewnych warto&#x15B;ci w systemie binarnym a jedynie pewn&#x105; aproksymacj&#x119; tej warto&#x15B;ci np. 0.09999999999999998 zamiast 0.1. Je&#x17C;eli chodzi o warto&#x15B;ci pieni&#x119;&#x17C;ne chcemy podczas oblicze&#x144; dok&#x142;adnie odwzorowa&#x107; rzeczywisto&#x15B;&#x107; tj. nie chcemy sytuacji, w kt&#xF3;rej np. na koncie bankowym klienta jest kwota mniejsza ni&#x17C; to by wynika&#x142;o z dokonanych transakcji. Z pomoc&#x105; przychodzi nam typ <em>BigDecimal</em> w j&#x119;zyku Java, kt&#xF3;ry pozwala nam kontrolowa&#x107; precyzj&#x119; i zapewni&#x107; dok&#x142;adno&#x15B;&#x107; oblicze&#x144;.</p></div></div><div class="kg-card kg-toggle-card" data-kg-toggle-state="close"><div class="kg-toggle-heading"><h4 class="kg-toggle-heading-text">11. Do czego s&#x142;u&#x17C;y <em>BindingResult</em> w Springu?</h4><button class="kg-toggle-card-icon"><svg id="Regular" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path class="cls-1" d="M23.25,7.311,12.53,18.03a.749.749,0,0,1-1.06,0L.75,7.311"/></svg></button></div><div class="kg-toggle-content"><p><em>BindingResult</em> jest interfejsem, kt&#xF3;ry definiuje w jaki spos&#xF3;b zbieramy wyniki walidacji obiektu, kt&#xF3;ry walidujemy. Domy&#x15B;lnie spring rzuci wyj&#x105;tkiem je&#x17C;eli dane, kt&#xF3;re walidujemy nie s&#x105; poprawne. Dzi&#x119;ki <em>BindingResult</em> mo&#x17C;emy mie&#x107; dost&#x119;p do wyniku walidacji przeprowadzonej przez framework i r&#x119;cznie zdecydowa&#x107; co dalej np:</p><p>@PostMapping(&quot;/register&quot;)<br>public ResponseEntity&lt;?&gt; register(@Valid @RequestBody RegisterRequest request, BindingResult, result) {<br> if (result.hasErrors()) {<br> return ResponseEntity.badRequest().body(result.toString());<br> }<br> registerService.register(request);<br> return ResponseEntity.ok().build();<br>}</p></div></div><div class="kg-card kg-toggle-card" data-kg-toggle-state="close"><div class="kg-toggle-heading"><h4 class="kg-toggle-heading-text">12. Wymie&#x144; poziomy izolacji transakcji i wyja&#x15B;nij jak dzia&#x142;a jeden z nich?</h4><button class="kg-toggle-card-icon"><svg id="Regular" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path class="cls-1" d="M23.25,7.311,12.53,18.03a.749.749,0,0,1-1.06,0L.75,7.311"/></svg></button></div><div class="kg-toggle-content"><p>Wyr&#xF3;&#x17C;niamy nast&#x119;puj&#x105;ce poziomy izolacji transakcji:</p><ul><li><em>READ_UNCOMMITTED</em></li><li><em>READ_COMMITTED</em></li><li><em>REPEATABLE_READ</em></li><li><em>SERIALIZABLE</em></li></ul><p><em>READ_COMMITTED - </em>transakcja z ustawionym takim trybem izolacji b&#x119;dzie &#x201E;widzia&#x142;a&#x201D; tylko zacommitowane zmiany co oznacza, &#x17C;e ponowne odczyty mog&#x105; zwraca&#x107; r&#xF3;&#x17C;ne wyniki z uwagi na inne transakcje, kt&#xF3;re wykonuj&#x105; si&#x119; w tym samym czasie. Aby zapewni&#x107; indempotentno&#x15B;&#x107; na poziomie odczytu (pojedy&#x144;czego rekordu) transakcji tj. aby ka&#x17C;dy kolejny odczyt zwraca&#x142; ten sam wynik mo&#x17C;emy zwi&#x119;kszy&#x107; poziom izolacji na <em>REPEATABLE_READ. </em>Warto zaznaczy&#x107;, &#x17C;e mimo ustawienia <em>REPETABLE_READ</em> mo&#x17C;e wyst&#x105;pi&#x107; tzw. <em>phantom read</em>, w kt&#xF3;rym pomi&#x119;dzy odczytami w jednej transakcji inne transakcje dodaj&#x105;/usuwaj&#x105; rekordy w tym samym czasie co prowadzi do r&#xF3;&#x17C;nych rezultat&#xF3;w na poziomie odczytu. Je&#x17C;eli chcemy uodporni&#x107; si&#x119; na <em>phantom read</em> mo&#x17C;emy ustawi&#x107; jeszcze bardziej restrykcyjny poziom izolacji tj. <em>SERIALIZABLE</em> jednak z pewno&#x15B;ci&#x105; wp&#x142;ynie to negatywnie na wydajno&#x15B;&#x107;.</p></div></div><div class="kg-card kg-toggle-card" data-kg-toggle-state="close"><div class="kg-toggle-heading"><h4 class="kg-toggle-heading-text">13. Jakie w&#x142;a&#x15B;ciwo&#x15B;ci ma adnotacja <em>@Transactional? </em>Co mo&#x17C;na ustawi&#x107;?</h4><button class="kg-toggle-card-icon"><svg id="Regular" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path class="cls-1" d="M23.25,7.311,12.53,18.03a.749.749,0,0,1-1.06,0L.75,7.311"/></svg></button></div><div class="kg-toggle-content"><p>Adnotacja <em>@Transactional</em> posiada m.in. nast&#x119;puj&#x105;ce w&#x142;a&#x15B;ciwo&#x15B;ci:</p><ul><li><em>isolation</em><br>poziom izolacji transakcji; domy&#x15B;lnie przyjmuj&#x119; warto&#x15B;&#x107; <em>Isolation.DEFAULT </em>czyli poziom izolacji b&#x119;dzie domy&#x15B;lny, zdeterminowany przez baz&#x119; danych, kt&#xF3;ra jest &#x201E;pod mask&#x105;&#x201D;</li><li><em>rollbackFor</em><br>okre&#x15B;la wyst&#x119;powanie jakich wyj&#x105;tk&#xF3;w powinno powodowa&#x107; wycofanie transakcji; domy&#x15B;lnie transakcja zostanie zrollbackowana gdy wyst&#x105;pi jeden z wyj&#x105;tk&#xF3;w typu <em>Error</em> lub <em>RuntimeException, </em>checked exceptions domy&#x15B;lnie nie powoduj&#x105; wycofania transakcji</li><li><em>noRollbackFor</em><br>okre&#x15B;la wyst&#x119;powanie jakich wyj&#x105;tk&#xF3;w nie powinno powodowa&#x107; wycofania transakcji</li><li><em>propagation</em><br>typ propagacji domy&#x15B;lnie <em>Propagation.REQUIRED</em></li><li><em>timeout</em><br>timeout dla transakcji w sekundach, domy&#x15B;lnie timeout po stronie aplikacji jest wy&#x142;&#x105;czony</li><li><em>readOnly</em><br>flaga okre&#x15B;laj&#x105;ca czy transakcja jest tylko do odczytu</li><li><em>transactionManager</em><br>nazwa beana odpowiedzialnego za zarz&#x105;dzanie transakcjami (mo&#x17C;emy mie&#x107; wiele transaction managerow w jednej aplikacji)</li></ul></div></div><div class="kg-card kg-toggle-card" data-kg-toggle-state="close"><div class="kg-toggle-heading"><h4 class="kg-toggle-heading-text">14. Jak walidowa&#x107; przychodz&#x105;ce requesty REST w Springu? Jaki b&#x142;&#x105;d wyst&#x105;pi gdy podamy stringa zamiast inta?</h4><button class="kg-toggle-card-icon"><svg id="Regular" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path class="cls-1" d="M23.25,7.311,12.53,18.03a.749.749,0,0,1-1.06,0L.75,7.311"/></svg></button></div><div class="kg-toggle-content"><p>W aplikacji spring boot&apos;owej gdy u&#x17C;ywamy zale&#x17C;no&#x15B;ci <em>spring-boot-starter-web</em> domy&#x15B;lnym formatem serializacji/deserializacji modelu transportowego jest <em>json </em>a biblioteka do tego to <em>jackson</em>. Gdy podamy &#x142;a&#x144;cuch znak&#xF3;w zamiast typu <em>integer </em>powstanie b&#x142;&#x105;d formatu czyli tak naprawd&#x119; b&#x142;&#x105;d deserializacji. <em>Jackson</em> zwr&#xF3;ci b&#x142;&#x105;d parsowania, objawi si&#x119; to wyj&#x105;tkiem typu <em>InvalidFormatException</em>.</p><p>Walidacj&#x119; modelu transportowego mo&#x17C;emy wykona&#x107; na r&#xF3;&#x17C;ne sposoby.<br>Warto zaznaczy&#x107;, &#x17C;e walidacj&#x119; mo&#x17C;emy podzieli&#x107; na 3 poziomy:</p><ul><li>walidacja formatu<br>Sprawdzamy czy komunikat poprawnie si&#x119; parsuje. Zwykle nie musimy ingerowa&#x107; w t&#x105; mo&#x17C;na powiedzie&#x107; niskopoziomow&#x105; walidacj&#x119;. Biblioteka, kt&#xF3;rej u&#x17C;ywamy powinna by&#x107; za to odpowiedzialna tj. w przyk&#x142;adzie wy&#x17C;ej.</li><li>walidacja struktury<br>Sprawdzamy czy ju&#x17C; przeparsowany komunikat jest zgodny z naszymi oczekiwaniami np. czy adres email jest poprawny, has&#x142;o jest w odpowiednim formacie itd. Nie bie&#x17C;emy pod uwag&#x119; aktualnego stanu systemu.</li><li>walidacja sp&#xF3;jno&#x15B;ci<br>Sprawdzamy czy komunikat/komenda mo&#x17C;e by&#x107; poprawnie wykonana bior&#x105;c pod uwag&#x119; aktualny stan systemu. Przyk&#x142;adowo nie mo&#x17C;emy zarejestrowa&#x107; 2x u&#x17C;ytkownika na ten sam email.</li></ul><p>Je&#x17C;eli chodzi o walidacj&#x119; struktury mo&#x17C;emy posi&#x142;kowa&#x107; si&#x119; bibliotek&#x105; <strong>hibernate-validator</strong> (implementacja standardu JSR 380). W Spring boot aby doda&#x107; wsparcie dla tego typu walidacji mo&#x17C;emy wykorzysta&#x107; <strong>spring-boot-starter-validation</strong>. Spring wspiera adnotacj&#x119; <em>@NotNull</em>, <em>@NotBlank</em>, <em>@Min</em>, <em>@Max</em> itd. w zwi&#x105;zku z tym obok request body w kontrolerach restowych mo&#x17C;emy u&#x17C;y&#x107; adnotacji <em>@Valid</em> aby dany model by&#x142; automatycznie zwalidowany.</p><p>Mo&#x17C;emy r&#xF3;wnie&#x17C; r&#x119;cznie walidowa&#x107; komunikaty w odpowiednio przygotowanych do tego klasach je&#x17C;eli np. mamy jakie&#x15B; specyficzne wymagania lub po prostu chcemy mie&#x107; pe&#x142;n&#x105; kontrol&#x119; i preferujemy taki spos&#xF3;b na walidacj&#x119;.</p></div></div><div class="kg-card kg-toggle-card" data-kg-toggle-state="close"><div class="kg-toggle-heading"><h4 class="kg-toggle-heading-text">15. Jak by&#x15B; zabezpieczy&#x142; api restowe?</h4><button class="kg-toggle-card-icon"><svg id="Regular" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path class="cls-1" d="M23.25,7.311,12.53,18.03a.749.749,0,0,1-1.06,0L.75,7.311"/></svg></button></div><div class="kg-toggle-content"><p>Api restowe mo&#x17C;emy zabezpieczy&#x107; na wiele sposob&#xF3;w. Jednym z nich mo&#x17C;e by&#x107; autoryzacja z wykorzystaniem tokena JWT (<em>JSON Web Token</em>). Natomiast ca&#x142;a komunikacja powinna by&#x107; zabezpieczona z wykorzystaniem protoko&#x142;u TLS.</p></div></div><div class="kg-card kg-toggle-card" data-kg-toggle-state="close"><div class="kg-toggle-heading"><h4 class="kg-toggle-heading-text">16. Jak przechowywa&#x107; has&#x142;a w bazie danych? Jak dzia&#x142;a Bcrypt?</h4><button class="kg-toggle-card-icon"><svg id="Regular" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path class="cls-1" d="M23.25,7.311,12.53,18.03a.749.749,0,0,1-1.06,0L.75,7.311"/></svg></button></div><div class="kg-toggle-content"><p>Rekomendowanym podej&#x15B;ciem do bezpiecznego przechowywania hase&#x142; w bazie danych jest u&#x17C;ycie jednokierunkowej funkcji hashuj&#x105;cej z sol&#x105;. Jednym z algorytm&#xF3;w implementuj&#x105;cych to podej&#x15B;cie jest bcrypt. Bcrypt jest funkcj&#x105; hashuj&#x105;c&#x105; dedykowan&#x105; do przechowywania hase&#x142; w bezpieczny spos&#xF3;b opart&#x105; o szyfr Blowish. Bcrypt na podstawie has&#x142;a lub innego dowolnego ci&#x105;gu zwr&#xF3;ci nam 60 znakowy hash has&#x142;a. Budowa wyj&#x15B;ciowego skr&#xF3;tu wygl&#x105;da nast&#x119;puj&#x105;co:</p><p>$2[wersja]$[koszt]$[s&#xF3;l][hash]</p><p>gdzie:</p><p>wersja - identyfikator algorytmu, domy&#x15B;lnie &apos;a&apos; czyli bcrypt<br>koszt - liczba z przedzia&#x142;u 04-99 okre&#x15B;laj&#x105;ca tzw. work factor algorytmu, domy&#x15B;lnie 12 czyli 2^12 = 4096 rund, ka&#x17C;de zwi&#x119;kszenie wsp&#xF3;&#x142;czynnika o jeden zwi&#x119;ksza dwukrotnie czas oblicze&#x144;<br>s&#xF3;l - losowy ci&#x105;g o wielko&#x15B;ci 22 znak&#xF3;w<br>hash - hash o wielko&#x15B;ci 31 znak&#xF3;w</p><p>Przyk&#x142;ad hasha dla has&#x142;a &apos;java&apos;:<br>$2a$12$1gUpfSyUB.OaegFGSe/3rOrLiuZCGauzK2nAqVWaBCA7vRWfLAC2a</p><p>Bcrypt jest uznawany za skuteczny i bezpieczny algorytm. S&#xF3;l pe&#x142;ni rol&#x119; mitygacji przed atakami z wykorzystaniem <a href="https://pl.wikipedia.org/wiki/T%C4%99czowe_tablice?ref=lsdev.pl">t&#x119;czowych tablic</a> a odpowiednio ustawiony &#x201E;work factor&#x201D; algorytmu powoduje, &#x17C;e czas &#x142;amania has&#x142;a znacz&#x105;co si&#x119; wyd&#x142;u&#x17C;a z uwagi na z&#x142;o&#x17C;ono&#x15B;&#x107; obliczeniow&#x105;, kt&#xF3;ra ro&#x15B;nie wyk&#x142;adniczo w zwi&#x105;zku z tym &#x142;amanie metod&#x105; si&#x142;ow&#x105; przestaje si&#x119; op&#x142;aca&#x107;.</p><p>Wi&#x119;cej na temat bezpiecze&#x144;stwa hase&#x142; mo&#x17C;emy przeczyta&#x107; <a href="https://sekurak.pl/kompendium-bezpieczenstwa-hasel-atak-i-obrona/?ref=lsdev.pl">tutaj</a>.</p></div></div><div class="kg-card kg-toggle-card" data-kg-toggle-state="close"><div class="kg-toggle-heading"><h4 class="kg-toggle-heading-text">17. Jakie znasz zalety / wady stream api w Java?</h4><button class="kg-toggle-card-icon"><svg id="Regular" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path class="cls-1" d="M23.25,7.311,12.53,18.03a.749.749,0,0,1-1.06,0L.75,7.311"/></svg></button></div><div class="kg-toggle-content"><p>Zalety:<br>- zwi&#x119;z&#x142;o&#x15B;&#x107; i czytelno&#x15B;&#x107; szczeg&#xF3;lnie w po&#x142;&#x105;czeniu z referencjami do metod<br>- elastyczno&#x15B;&#x107;, mo&#x17C;emy bardzo &#x142;atwo zmienia&#x107; kolejno&#x15B;&#x107; krok&#xF3;w w algorytmie<br>- dzi&#x119;ki parallelStream() mo&#x17C;emy w prosty spos&#xF3;b zr&#xF3;wnolegli&#x107; operacje na danych<br><br>Wady:<br>- utrudnia debugowanie, chocia&#x17C; IDE coraz lepiej sobie z tym radz&#x105;<br>- wyra&#x17C;enia (lambda) w map/filter itd. nie mog&#x105; rzuca&#x107; tzw. checked exceptions co mo&#x17C;e by&#x107; problematyczne np. jak przetwarzamy I/O</p></div></div><div class="kg-card kg-toggle-card" data-kg-toggle-state="close"><div class="kg-toggle-heading"><h4 class="kg-toggle-heading-text">18. Jak dzia&#x142;a protok&#xF3;&#x142; HTTP? Jakie s&#x105; metody?</h4><button class="kg-toggle-card-icon"><svg id="Regular" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path class="cls-1" d="M23.25,7.311,12.53,18.03a.749.749,0,0,1-1.06,0L.75,7.311"/></svg></button></div><div class="kg-toggle-content"><p>HTTP (<em>Hypertext Transfer Protocol</em>) jest protoko&#x142;em warstwy aplikacji, kt&#xF3;ry definiuje komunikacj&#x119; pomi&#x119;dzy przegl&#x105;dark&#x105; a serwerem HTTP. W tym modelu serwer HTTP jest odpowiedzialny za obs&#x142;ug&#x119; &#x17C;&#x105;da&#x144; pochodz&#x105;cych od klienta - przewa&#x17C;nie przegl&#x105;darki internetowej. Klient jest inicjatorem po&#x142;&#x105;czenia a wi&#x119;c wszelkie zasoby, skrypty, obrazki s&#x105; przesy&#x142;ane przez serwer HTTP do klienta w ramach odpowiedzi na &#x17C;&#x105;danie.</p><p>Wspomnia&#x142;em o tym, &#x17C;e klientem jest przewa&#x17C;nie przegl&#x105;darka internetowa natomiast w praktyce mog&#x105; to by&#x107; r&#xF3;wnie&#x17C; inne aplikacje dzia&#x142;aj&#x105;ce w imieniu u&#x17C;ytkownika np. urz&#x105;dzenie mobilne albo inny system, kt&#xF3;ry si&#x119; z nami integruje. Rodzaj &#x201E;narz&#x119;dzia&#x201D;, kt&#xF3;re komunikuje si&#x119; z serwerem jest zdefinowany przez nag&#x142;&#xF3;wek <em>User-Agent</em>.</p><p>Warto wspomnie&#x107;, &#x17C;e protok&#xF3;&#x142; HTTP jest protoko&#x142;em bezstanowym co oznacza, &#x17C;e ka&#x17C;de &#x17C;&#x105;danie do serwera jest interpretowane oddzielnie i nie ma pomi&#x119;dzy nimi logicznego powi&#x105;zania. Po&#x142;&#x105;czenie TCP, na bazie kt&#xF3;rego opiera si&#x119; ten protok&#xF3;&#x142; zwykle jest zamykane i nie jest utrzymywane chyba, &#x17C;e ze wzgl&#x119;d&#xF3;w wydajno&#x15B;ciowych chcemy utrzyma&#x107; po&#x142;&#x105;czenie mo&#x17C;emy &#x201E;powiedzie&#x107;&#x201D; o tym serwerowi u&#x17C;ywaj&#x105;c nag&#x142;&#xF3;wka <em>Connection: keep-alive</em> (<a href="https://en.wikipedia.org/wiki/HTTP_persistent_connection?ref=lsdev.pl">HTTP Persistent Connection</a>).</p><p>Komunikaty (&#x17C;&#x105;dania i odpowiedzi) w protokole HTTP zbudowane s&#x105; z nag&#x142;&#xF3;wk&#xF3;w czyli metadanych oraz payload&apos;u (lub body), kt&#xF3;ry zawiera odpowied&#x17A;, o kt&#xF3;r&#x105; klient &#x17C;&#x105;da&#x142;, mo&#x17C;e to by&#x107; np. strona www. Ka&#x17C;de &#x17C;&#x105;danie zawiera r&#xF3;wnie&#x17C; tzw. metod&#x119; HTTP, kt&#xF3;ra definiuje akcj&#x119; jak&#x105; chcemy wykona&#x107; w ramach danego zasobu.</p><p>Wyr&#xF3;&#x17C;niamy nast&#x119;puj&#x105;ce podstawowe metody HTTP:</p><p><strong>GET</strong> - pobranie lub odczytanie zasobu, &#x17C;&#x105;danie tego typu nie powinno zmienia&#x107; stanu na serwerze w kontek&#x15B;cie danego zasobu.</p><p><strong>HEAD</strong> - &#x17C;&#x105;danie podobne do <strong>GET</strong> z takim wyj&#x105;tkiem, &#x17C;e nie zwraca body. U&#x17C;yteczne gdy chcemy sprawdzi&#x107; tylko metadane/nag&#x142;&#xF3;wki zasobu bez narzutu na transfer np. mo&#x17C;emy sprawdzi&#x107; czy dany plik istnieje i jaki ma rozmiar bez jego pobierania.</p><p><strong>POST</strong> - wys&#x142;anie danych na serwer, zwykle gdy wype&#x142;niamy jaki&#x15B; formularz korzystamy z tej metody aby przes&#x142;a&#x107; wype&#x142;nione dane na serwer po czym serwer tworzy nowy zas&#xF3;b.</p><p><strong>PUT</strong> - wysy&#x142;anie danych na serwer w celu aktualizacji istniej&#x105;cego zasobu.</p><p><strong>DELETE</strong> - usuni&#x119;cie istniej&#x105;cego zasobu.</p><p><strong>PATCH</strong> - zmiana jakiej&#x15B; cz&#x119;&#x15B;ci istniej&#x105;cego zasobu np. zmiana has&#x142;a u&#x17C;ytkownika.</p><p>Wi&#x119;cej o protokole HTTP mo&#x17C;na przeczyta&#x107; <a href="https://developer.mozilla.org/pl/docs/Web/HTTP/Overview?ref=lsdev.pl">tutaj</a>.</p></div></div><div class="kg-card kg-toggle-card" data-kg-toggle-state="close"><div class="kg-toggle-heading"><h4 class="kg-toggle-heading-text">19. Jak wygl&#x105;da model pami&#x119;ci w Java? Jakie znasz Garbage Collectory? Jakie maj&#x105; zastosowanie?</h4><button class="kg-toggle-card-icon"><svg id="Regular" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path class="cls-1" d="M23.25,7.311,12.53,18.03a.749.749,0,0,1-1.06,0L.75,7.311"/></svg></button></div><div class="kg-toggle-content"><p>Maszyna wirtualna Javy jest zwyk&#x142;ym procesem w systemie operacyjnym, kt&#xF3;ry tj. ka&#x17C;dy inny proces potrzebuje pami&#x119;ci do funkcjonowania. Aby programy napisane w Java funkcjonowa&#x142;y w poprawny spos&#xF3;b wirtualna maszyna dzieli pami&#x119;&#x107; na r&#xF3;&#x17C;ne obszary, kt&#xF3;re maj&#x105; odpowiednie zastosowanie:</p><ul><li>heap (sterta)<br>Pami&#x119;&#x107; wykorzystywana do przechowywania obiekt&#xF3;w, kt&#xF3;re tworzymy w naszej aplikacji. Utworzone obiekty &#x17C;yj&#x105; dop&#xF3;ki ich u&#x17C;ywamy tj. mamy wskazanie do nich przez referencje. Kiedy na dany obiekt nie wskazuje ju&#x17C; &#x17C;adna referencja obiekt jest niszczony przez algorytm Garbage Collectora, kt&#xF3;ry jest odpowiedzialny za zwalnianie pami&#x119;ci przez obiekty, kt&#xF3;re ju&#x17C; nie s&#x105; u&#x17C;ywane.<br><br>Heap r&#xF3;wnie&#x17C; mo&#x17C;emy podzieli&#x107; na 2 obszary tzw. <em>Young Generation</em> (kt&#xF3;ry dzieli si&#x119; r&#xF3;wnie&#x17C; na Eden, Survivor0 i Survivor1) i <em>Old Generation</em>. W Java istnieje tzw. hipoteza generacyjna, kt&#xF3;ra m&#xF3;wi, &#x17C;e tzw. &#x201E;m&#x142;ode obiekty&#x201D; &#x17C;yj&#x105; kr&#xF3;cej ni&#x17C; stare tj. istnieje wi&#x119;ksze prawdopodobie&#x144;stwo, &#x17C;e obiekt, kt&#xF3;ry prze&#x17C;y&#x142; kilka lub kilkana&#x15B;cie cykli GC b&#x119;dzie &#x17C;y&#x142; dalej ni&#x17C; obiekt, kt&#xF3;ry dopiero co zosta&#x142; stworzony. Dlatego te&#x17C; obiekty, kt&#xF3;re &#x201E;swoje prze&#x17C;y&#x142;y&#x201D; przerzucane s&#x105; do <em>Old Generation</em>. Podzia&#x142; ten pozwala na dostosowanie odpowiednich algorytm&#xF3;w Garbage Collectora do danego obszaru pami&#x119;ci np. dla przestrzeni <em>Young Generation</em> b&#x119;dziemy chcieli cz&#x119;&#x15B;ciej uruchomi&#x107; skanowanie GC ni&#x17C; dla <em>Old Generation</em>.</li><li>non-heap<br>Natywna pami&#x119;&#x107; maszyny wirtualnej. W tej przestrzeni zapisywane s&#x105; obiekty potrzebne maszynie wirtualnej do poprawnego funkcjonowania. Wyr&#xF3;&#x17C;niamy m.in. takie obszary jak:<br><br>- metaspace<br> Zawiera m.in. kod naszej aplikacji, constant poole, dane p&#xF3;l i metod<br><br>- code cache<br> Przestrze&#x144; m.in. do zapami&#x119;tywania kodu natywnego przez JITa, o kt&#xF3;rym wspomnia&#x142;em wcze&#x15B;niej<br><br>- stos dla w&#x105;tk&#xF3;w</li></ul><p>Garbage collectory w JVM u&#x17C;ywane s&#x105; do od&#x15B;miecania pami&#x119;ci czyli zwalniania nieu&#x17C;ywanych obiekt&#xF3;w. Dzi&#x119;ki temu nie musimy r&#x119;cznie zarz&#x105;dza&#x107; pami&#x119;ci&#x105; tj. w j&#x119;zykach C/C++, mo&#x17C;emy zrzuci&#x107; t&#x119; odpowiedzialno&#x15B;&#x107; na maszyn&#x119; wirtualn&#x105; w tym przypadku na konkretny algorytm Garbage Collectora. Wyr&#xF3;&#x17C;niamy m.in. nast&#x119;puj&#x105;ce algorytmy GC:</p><ul><li>Serial</li><li>Parallel</li><li>Concurrent Mark Sweep</li><li>G1</li></ul><p>Domy&#x15B;lnie od Javy 9 u&#x17C;ywany jest algorytm G1. Mamy mo&#x17C;liwo&#x15B;&#x107; zmiany algorytmu przekazuj&#x105;c odpowiednie parametry do JVM podczas uruchamiania aplikacji np:</p><p>java -XX:+UseParallelGC -jar Application.java</p></div></div><div class="kg-card kg-toggle-card" data-kg-toggle-state="close"><div class="kg-toggle-heading"><h4 class="kg-toggle-heading-text">20. Jakie znasz typy atak&#xF3;w webowych? Wymie&#x144; i obja&#x15B;nij jeden z nich.</h4><button class="kg-toggle-card-icon"><svg id="Regular" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path class="cls-1" d="M23.25,7.311,12.53,18.03a.749.749,0,0,1-1.06,0L.75,7.311"/></svg></button></div><div class="kg-toggle-content"><p>Mo&#x17C;emy wyr&#xF3;&#x17C;ni&#x107; m.in. takie ataki jak XSS (<em>Cross-site scripting</em>), SQLI (<em>SQL Injection</em>), Path Traversal, DDoS (Distributed Denial of Service), SSRF (<em>Server-Side Request Forgery</em>).</p><p>Atak SQLI polega na zmanipulowaniu wej&#x15B;cia (np. nazwy u&#x17C;ytkownika lub frazy wyszukiwania) w taki spos&#xF3;b aby silnik bazodanowy podczas parsowania tego zapytania zinterpretowa&#x142; je inaczej ni&#x17C; tw&#xF3;rca aplikacji za&#x142;o&#x17C;y&#x142; tj. zmieniaj&#x105;c to zapytanie. Atakuj&#x105;cy maj&#x105;c kontrol&#x119; nad zapytaniem zyskuje dost&#x119;p do danych w bazie danych, do kt&#xF3;rych nie powinien mie&#x107; dost&#x119;pu.</p><p>Aby nasza aplikacja nie by&#x142;a podatna na ten typ ataku powinni&#x15B;my zastosowa&#x107; tzw. <em>prepared statement. </em>Baza danych wtedy parsuje samo zapytanie oddzielnie kolejno wstrzykuj&#x105;c podane warto&#x15B;ci w odpowiednie miejsca w zapytaniu co nie tylko poprawia bezpiecze&#x144;stwo ale te&#x17C; wydajno&#x15B;&#x107; gdy&#x17C; zapytanie nie musi by&#x107; wielokrotnie parsowane przez silnik bazodanowy.</p></div></div><!--kg-card-begin: html--><p>Gratulacje dotrwa&#x142;e&#x15B; do ko&#x144;ca! Je&#x17C;eli ten artyku&#x142; by&#x142; dla Ciebie warto&#x15B;ciowy podziel si&#x119; nim z innymi <i class="fas fa-smile-wink fa-lg" style="color: var(--galaxy-color)"></i></p><!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[SOP (Same-Origin Policy), CORS (Cross-Origin Resource Sharing) i przykładowa konfiguracja w Springu]]></title><description><![CDATA[<p><strong>SOP</strong> (<em>Same-Origin Policy</em>) jest mechanizmem bezpiecze&#x144;stwa przegl&#x105;darek internetowych<br>polegaj&#x105;cy na odizolowaniu od siebie zasob&#xF3;w r&#xF3;&#x17C;nego pochodzenia. Ka&#x17C;dy zas&#xF3;b webowy ma &#x17A;r&#xF3;d&#x142;o, z kt&#xF3;rego pochodzi tzw. <em>origin</em>. Jeden unikalny</p>]]></description><link>https://lsdev.pl/posts/czym-jest-cors-sop-i-przykladowa-konfiguracja-w-springu/</link><guid isPermaLink="false">61daff52fed1d8a4024d1582</guid><category><![CDATA[Spring]]></category><category><![CDATA[Security]]></category><category><![CDATA[Java]]></category><category><![CDATA[Programming]]></category><dc:creator><![CDATA[Łukasz]]></dc:creator><pubDate>Sun, 09 Jan 2022 23:11:44 GMT</pubDate><media:content url="https://lsdev.pl/content/images/2022/01/photo-post-1.webp" medium="image"/><content:encoded><![CDATA[<img src="https://lsdev.pl/content/images/2022/01/photo-post-1.webp" alt="SOP (Same-Origin Policy), CORS (Cross-Origin Resource Sharing) i przyk&#x142;adowa konfiguracja w Springu"><p><strong>SOP</strong> (<em>Same-Origin Policy</em>) jest mechanizmem bezpiecze&#x144;stwa przegl&#x105;darek internetowych<br>polegaj&#x105;cy na odizolowaniu od siebie zasob&#xF3;w r&#xF3;&#x17C;nego pochodzenia. Ka&#x17C;dy zas&#xF3;b webowy ma &#x17A;r&#xF3;d&#x142;o, z kt&#xF3;rego pochodzi tzw. <em>origin</em>. Jeden unikalny <em>origin</em> definiuj&#x105; jego 3 g&#x142;&#xF3;wne sk&#x142;adowe tj. <code>protok&#xF3;&#x142;</code>, <code>domena</code> i <code>port</code>.<br><br>Przyk&#x142;adowo <br><br><code>http://api.example.com/app.js</code><br><code>http://api.example.com:8090/app.js</code><br><code>http://example.com/app.js</code> <br><code>https://example.com/app.js</code><br><br>s&#x105; r&#xF3;&#x17C;nymi originami mimo, &#x17C;e mog&#x105; wskazywa&#x107; na ten sam zas&#xF3;b. Z drugiej strony<br><br><code>http://example.com/example1.html</code> <br><code>http://example.com/example2.html</code> <br><br>s&#x105; r&#xF3;&#x17C;nymi zasobami jednak pochodz&#x105; z tego samego <em>origin&apos;a</em>.</p><p>W praktyce pod jednym <em>origin</em> zwykle mamy aplikacj&#x119; webow&#x105;<br>sk&#x142;adaj&#x105;c&#x105; si&#x119; z wielu r&#xF3;&#x17C;nych zasob&#xF3;w aby jej funkcjonowanie by&#x142;o mo&#x17C;liwe.</p><p>Przegl&#x105;darka implementuj&#x105;c <strong>SOP</strong> w podstawowym zakresie zabezpiecza nasz&#x105; aplikacj&#x119;<br>przed r&#xF3;&#x17C;nego rodzaju potencjalnym zagro&#x17C;eniem ze strony bezpiecze&#x144;stwa gdzie inna aplikacja np. stworzona przez atakuj&#x105;cego mog&#x142;aby z wykorzystaniem <a href="https://pl.wikipedia.org/wiki/AJAX?ref=lsdev.pl">AJAX</a> wywo&#x142;ywa&#x107; metody w imieniu u&#x17C;ytkownika naszej aplikacji np. kupi&#x107; produkt w sklepie uprzednio zmieniaj&#x105;c adres wysy&#x142;ki na atakuj&#x105;cego czyli tzw. atak <a href="https://pl.wikipedia.org/wiki/Cross-site_request_forgery?ref=lsdev.pl">CSRF</a> (<em>Cross-site request forgery</em>). Warto wspomnie&#x107;, &#x17C;e nie nale&#x17C;y polega&#x107; jedynie na tym mechani&#x17A;mie i stosowa&#x107; r&#xF3;&#x17C;nego rodzaju zabezpiczenia po stronie serwera tj. &#xA0;csrf token, walidacja origin, nag&#x142;&#xF3;wki access control i inne zabezpieczenia.</p><p>W celu przetestowania mechanizmu <strong>SOP</strong> wystarczy wej&#x15B;&#x107; na dowoln&#x105; stron&#x119; np. <code>google.com</code> otworzy&#x107; narz&#x119;dzia developerskie w zak&#x142;adce <code>Console</code> i wys&#x142;a&#x107; &#x17C;&#x105;danie <em>Ajaxowe</em> na inny <em>origin</em>:</p><!--kg-card-begin: html--><pre><code class="language-js">var req = new XMLHttpRequest();
req.open(&apos;GET&apos;, &apos;http://localhost:8081&apos;, false);
req.send();
</code></pre><!--kg-card-end: html--><!--kg-card-begin: html--><small><span style="color: var(--red-color); font-weight: bolder">Access to XMLHttpRequest at &apos;http://localhost:8081/&apos; from origin &apos;https://www.google.com&apos; has been blocked by CORS policy: No &apos;Access-Control-Allow-Origin&apos; header is present on the requested resource.</span></small><!--kg-card-end: html--><p>Jak wida&#x107; przegl&#x105;darka zablokowa&#x142;a mo&#x17C;liwo&#x15B;&#x107; odczytania / dost&#x119;pu do odpowiedzi z uwagi na inny <em>origin</em>, z kt&#xF3;rego pobieramy zas&#xF3;b ni&#x17C; ten, w kontek&#x15B;cie kt&#xF3;rego aktualnie si&#x119; znajdujemy czyli <code>google.com</code>. Warto zaznaczy&#x107;, &#x17C;e przegl&#x105;darka jedynie zablokowa&#x142;a dost&#x119;p do odpowiedzi natomiast samo &#x17C;&#x105;danie zosta&#x142;o wys&#x142;ane poprawnie. Wynika to z faktu, &#x17C;e przegl&#x105;darka dzieli &#x17C;&#x105;dania na &#x201E;proste&#x201D; czyli np. wykonywane w kontek&#x15B;cie <code>img</code>, <code>iframe</code>, <code>style</code>, <code>form</code> i &#x201E;z&#x142;o&#x17C;one&#x201D; np. typu <code>PUT</code>, <code>DELETE</code> (wi&#x119;cej o r&#xF3;&#x17C;nicy <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS?ref=lsdev.pl#examples_of_access_control_scenarios">tutaj</a>).</p><p>Wiemy ju&#x17C; dlaczego nie mo&#x17C;emy polega&#x107; tylko na mechani&#x17A;mie <strong>SOP</strong>. Atakuj&#x105;cy w &#x142;atwy spos&#xF3;b m&#xF3;g&#x142;by spreparowa&#x107; tzw. &#x201E;proste&#x201D; &#x17C;&#x105;danie przygotowuj&#x105;c np. odpowiedni<br>formularz, kt&#xF3;ry automatycznie by zosta&#x142; wys&#x142;any do serwera zmieniaj&#x105;c stan systemu w niekontrolowany spos&#xF3;b.</p><p>Z uwagi na to, &#x17C;e <strong>SOP</strong> jest do&#x15B;&#x107; restrykcyjny poniewa&#x17C; pozwala na dost&#x119;p do zasob&#xF3;w tylko z tego samego <em>origin, </em><strong>CORS </strong>(<em>Cross-Origin Resource Sharing</em>) ma za zadanie zluzowa&#x107; t&#x105; sztywn&#x105; polityk&#x119; i umo&#x17C;liwi&#x107; za pomoc&#x105; odpowiedniej konfiguracji na bezproblemow&#x105; komunikacj&#x119; pomi&#x119;dzy r&#xF3;&#x17C;nymi <em>origin&apos;ami</em>.</p><p>Przyk&#x142;adowo aby odblokowa&#x107; dost&#x119;p do odpowiedzi, kt&#xF3;ra wcze&#x15B;niej by&#x142;a przyblokowana przez przegl&#x105;dark&#x119; mo&#x17C;emy po stronie serwera w odpowiedzi ustawi&#x107; nag&#x142;&#xF3;wek w taki spos&#xF3;b:</p><p><code>Access-Control-Allow-Origin: *</code></p><p>W ten spos&#xF3;b zapewnimy dost&#x119;p dla danego zasobu wszystkim aplikacjom (<em>origins</em>), kt&#xF3;re b&#x119;d&#x105; chcia&#x142;y uzyska&#x107; do niego dost&#x119;p. Zwykle powy&#x17C;szy nag&#x142;&#xF3;wek ustawiany jest w taki spos&#xF3;b dla zasob&#xF3;w statycznych dost&#x119;pnych publicznie np. biblioteki javascriptowe. Jest to typowe dla sieci CDN:</p><!--kg-card-begin: html--><pre><code class="language-bash">$ curl -I https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js

HTTP/2 200 
content-type: application/javascript; charset=utf-8
access-control-allow-origin: *
server: cloudflare
...
</code></pre><!--kg-card-end: html--><h3 id="przyk%C5%82adowa-konfiguracja-cors-w-springu">Przyk&#x142;adowa konfiguracja CORS w Springu</h3><p>Mo&#x17C;e si&#x119; zdarzy&#x107; tak, &#x17C;e aplikacja kliencka po stronie przegl&#x105;darki (np. Angular) b&#x119;dzie musia&#x142;a komunikowa&#x107; si&#x119; z api serwera w innym <em>origin </em>ni&#x17C; jest serwowana np. aplikacja napisana w Angular jest dost&#x119;pna pod adresem <code>https://example.com</code> i komunikuje si&#x119; z backendem poprzez <code>https://api.example.com</code>. Wiemy ju&#x17C;, &#x17C;e s&#x105; to dwa r&#xF3;&#x17C;ne <em>origin&apos;y </em>w zwi&#x105;zku z tym aby komunikacja przebieg&#x142;a pomy&#x15B;lnie musimy w taki spos&#xF3;b skonfigurowa&#x107; backend aby zezwoli&#x107; na tak&#x105; komunikacj&#x119;.</p><p>Rozpoczniemy od zdefiniowania beana odpowiedzialnego za dostarczenie ww. konfiguracji:</p><!--kg-card-begin: html--><pre><code class="language-java line-numbers">import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.List;

@Configuration
@ConditionalOnProperty(prefix = &quot;cors&quot;, name = &quot;allowedOrigins&quot;)
public class CorsConfiguration {

    @Bean
    public CorsConfigurationSource corsConfigurationSource(
            @Value(&quot;${cors.allowedOrigins}&quot;) List<string> allowedOrigins
    ) {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        org.springframework.web.cors.CorsConfiguration corsConfiguration = new org.springframework.web.cors.CorsConfiguration();
        corsConfiguration.setAllowedOrigins(allowedOrigins);
        corsConfiguration.addAllowedHeader(&quot;*&quot;);
        corsConfiguration.setAllowedMethods(List.of(
                HttpMethod.GET.name(),
                HttpMethod.HEAD.name(),
                HttpMethod.POST.name(),
                HttpMethod.PUT.name(),
                HttpMethod.DELETE.name()
        ));
        corsConfiguration.setMaxAge(3600L);
        source.registerCorsConfiguration(&quot;/**&quot;, corsConfiguration);
        return source;
    }
}
</string></code></pre><!--kg-card-end: html--><p> Nast&#x119;pnie stworzymy adnotacj&#x119; pomocnicz&#x105;, kt&#xF3;ra zaimportuje nam ww. konfiguracj&#x119;.<br>Zak&#x142;adam, &#x17C;e konfiguracja b&#x119;dzie w jednej z bibliotek wsp&#xF3;lnych w zwi&#x105;zku z tym pozwoli to nam j&#x105; w&#x142;&#x105;cza&#x107; w zale&#x17C;no&#x15B;ci od potrzeb dla modu&#x142;&#xF3;w, kt&#xF3;re wystawiaj&#x105; api restowe. </p><!--kg-card-begin: html--><pre><code class="language-java line-numbers">import org.springframework.context.annotation.Import;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import({CorsConfiguration.class})
public @interface EnableCorsConfiguration {
}
</code></pre><!--kg-card-end: html--><p>Po czym wykorzystamy ww. konfiguracj&#x119;:</p><!--kg-card-begin: html--><pre><code class="language-java line-numbers">import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;

@Configuration
@EnableResourceServer
@EnableCorsConfiguration
public class ExampleSecurityConfiguration extends ResourceServerConfigurerAdapter {
    
    @Value(&quot;${oauth.resourceId}&quot;)
    private String resourceId;

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.requestMatchers().antMatchers(&quot;/**&quot;)
                    .anyRequest().authenticated().and().cors();
    }
    
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.resourceId(resourceId);
    }
}
</code></pre><!--kg-card-end: html--><p>Na koniec w pliku <code>application.yaml</code> zdefiniujemy na jakie <em>origin&apos;y </em>zezwalamy:</p><!--kg-card-begin: html--><pre><code class="language-js">cors:
  allowedOrigins: https://example.com
</code></pre><!--kg-card-end: html--><p>Od teraz aplikacja kliencka bez problemu powinna komunikowa&#x107; si&#x119; z backendem.<br><br>Warto wspomnie&#x107;, &#x17C;e je&#x17C;eli przed naszymi aplikacjami backendowymi w Java stoi jaki&#x15B; reverse proxy np. Nginx tam r&#xF3;wnie&#x17C; by&#x142;aby mo&#x17C;liwa konfiguracja poprzez manipulacj&#x119; nag&#x142;&#xF3;wkami. </p><p>Na koniec warto zastanowi&#x107; si&#x119; czy architektura, w kt&#xF3;rej API mamy na oddzielnej domenie faktycznie ma sens. Przeniesienie go na g&#x142;&#xF3;wn&#x105; domen&#x119; mo&#x17C;e zredukowa&#x107; liczb&#x119; wykonywanych &#x17C;&#x105;da&#x144; nawet o po&#x142;ow&#x119; (z uwagi na <em>Preflight Requests</em>) co na pewno odci&#x105;&#x17C;y nasz serwer.</p>]]></content:encoded></item></channel></rss>