{"id":79465,"date":"2020-04-27T07:42:20","date_gmt":"2020-04-27T05:42:20","guid":{"rendered":"https:\/\/prohoster.info\/blog\/administrirovanie\/istina-prezhde-vsego-ili-pochemu-sistemu-nuzhno-proektirovat-ishodya-iz-ustrojstva-bazy-dannyh"},"modified":"2020-04-27T07:42:20","modified_gmt":"2020-04-27T05:42:20","slug":"istina-prezhde-vsego-ili-pochemu-sistemu-nuzhno-proektirovat-ishodya-iz-ustrojstva-bazy-dannyh","status":"publish","type":"post","link":"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/istina-prezhde-vsego-ili-pochemu-sistemu-nuzhno-proektirovat-ishodya-iz-ustrojstva-bazy-dannyh","title":{"rendered":"Prawda przede wszystkim, czyli dlaczego system powinien by\u0107 projektowany z uwzgl\u0119dnieniem struktury bazy danych","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>Cze\u015b\u0107, Habr!<\/p>\n<p>Kontynuujemy badanie tematu <noindex><a rel=\"nofollow\" href=\"https:\/\/www.piter.com\/collection\/all\/product\/sovremennyy-yazyk-java-lyambda-vyrazheniya-potoki-i-funktsionalnoe-programmirovanie\">Java<\/a><\/noindex> i <noindex><a rel=\"nofollow\" href=\"https:\/\/storage.piter.com\/upload\/contents\/978544611587\/978544611587_X.pdf\">Spring<\/a><\/noindex>, w tym na poziomie baz danych. Dzi\u015b proponujemy przeczyta\u0107 o tym, dlaczego przy projektowaniu du\u017cych aplikacji to w\u0142a\u015bnie struktura bazy danych, a nie kod Java, powinna mie\u0107 kluczowe znaczenie, jak to zrobi\u0107 i jakie s\u0105 wyj\u0105tki od tej zasady.<br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex> <br \/>\nW tym do\u015b\u0107 sp\u00f3\u017anionym artykule wyja\u015bni\u0119, dlaczego uwa\u017cam, \u017ce w praktycznie wszystkich przypadkach model danych w aplikacji powinien by\u0107 projektowany \u201ena podstawie bazy danych\u201d, a nie \u201ena podstawie mo\u017cliwo\u015bci Java\u201d (lub innego j\u0119zyka klienckiego, z kt\u00f3rym pracujesz). Wybieraj\u0105c drugie podej\u015bcie, wkraczasz na d\u0142ug\u0105 drog\u0119 b\u00f3lu i cierpienia, gdy tylko tw\u00f3j projekt zaczyna rosn\u0105\u0107.<\/p>\n<p>Artyku\u0142 zosta\u0142 napisany na podstawie <noindex><a rel=\"nofollow\" href=\"https:\/\/stackoverflow.com\/questions\/50706556\/kotlin-with-jooq-and-write-table-models-manually-without-code-generation\">jednego pytania<\/a><\/noindex>, zadawanego na Stack Overflow.<\/p>\n<p>Interesuj\u0105ce dyskusje na reddicie w sekcjach <noindex><a rel=\"nofollow\" href=\"https:\/\/www.reddit.com\/r\/java\/comments\/8p0uj3\/truth_first_or_why_you_should_mostly_implement\/\">\/r\/java<\/a><\/noindex> i <noindex><a rel=\"nofollow\" href=\"https:\/\/www.reddit.com\/r\/programming\/comments\/8p0qle\/truth_first_or_why_you_should_mostly_implement\/\">\/r\/programming<\/a><\/noindex>.<\/p>\n<h4>Generowanie kodu<\/h4>\n<p>\nBy\u0142em bardzo zdziwiony, \u017ce jest tak niewielka grupa u\u017cytkownik\u00f3w, kt\u00f3rzy po zapoznaniu si\u0119 z jOOQ s\u0105 oburzeni faktem, \u017ce podczas pracy z jOOQ powa\u017cnie polega si\u0119 na generacji kodu. Nikt nie zabrania korzystania z jOOQ w spos\u00f3b, jaki uwa\u017casz za stosowny, i nie zmusza do u\u017cywania generacji kodu. Ale domy\u015blnie (jak opisano w dokumentacji) praca z jOOQ przebiega tak: zaczynasz z (odziedziczon\u0105) schem\u0105 bazy danych, wykonujesz jej odwrotne projektowanie przy pomocy generatora kodu jOOQ, aby uzyska\u0107 zestaw klas reprezentuj\u0105cych twoje tabele, a nast\u0119pnie piszesz typowo bezpieczne zapytania do tych tabel:<\/p>\n<pre><code class=\"java\">\tfor (Record2 record : DSL.using(configuration)\n\/\/   ^^^^^^^^^^^^^^^^^^^^^^^ Informacje o typach s\u0105 wyprowadzane na\n\/\/   podstawie wygenerowanego kodu, na kt\u00f3ry odnosi si\u0119 poni\u017cszy warunek SELECT\n\n       .select(ACTOR.FIRST_NAME, ACTOR.LAST_NAME)\n\/\/           vvvvv ^^^^^^^^^^^^  ^^^^^^^^^^^^^^^ wygenerowane nazwy\n       .from(ACTOR)\n       .orderBy(1, 2)) {\n    \/\/ ...\n}<\/code><\/pre>\n<p>\nKod jest generowany r\u0119cznie poza kompilacj\u0105 lub r\u0119cznie przy ka\u017cdej kompilacji. Na przyk\u0142ad taka regeneracja mo\u017ce nast\u0105pi\u0107 bezpo\u015brednio po <noindex><a rel=\"nofollow\" href=\"https:\/\/blog.jooq.org\/2014\/06\/25\/flyway-and-jooq-for-unbeatable-sql-development-productivity\/\">migracji bazy danych Flyway, kt\u00f3r\u0105 r\u00f3wnie\u017c mo\u017cna wykona\u0107 r\u0119cznie lub automatycznie.<\/a><\/noindex>.<\/p>\n<h4>Generacja kodu \u017ar\u00f3d\u0142owego<\/h4>\n<p>\nPodej\u015bcia do generowania kodu \u2013 r\u0119czne i automatyczne \u2013 wi\u0105\u017c\u0105 si\u0119 z r\u00f3\u017cnymi filozofiami, zaletami i wadami, kt\u00f3re nie zamierzam szczeg\u00f3\u0142owo omawia\u0107 w tym artykule. Jednak og\u00f3lnie rzecz bior\u0105c, istota generowanego kodu polega na tym, \u017ce pozwala on odwzorowa\u0107 w Javie t\u0119 \"prawd\u0119\", kt\u00f3r\u0105 przyjmujemy za dan\u0105, zar\u00f3wno w ramach naszego systemu, jak i poza nim. W pewnym sensie to samo robi\u0105 kompilatory, generuj\u0105c bytecode, kod maszynowy lub inny rodzaj kodu na podstawie \u017ar\u00f3de\u0142 \u2013 otrzymujemy przedstawienie naszej \"prawdy\" w innym j\u0119zyku, niezale\u017cnie od konkretnych powod\u00f3w. <\/p>\n<p>Istnieje wiele takich generator\u00f3w kodu. Na przyk\u0142ad, <noindex><a rel=\"nofollow\" href=\"https:\/\/docs.oracle.com\/javase\/8\/docs\/technotes\/tools\/unix\/xjc.html\">XJC mo\u017ce generowa\u0107 kod Java na podstawie plik\u00f3w XSD lub WSDL.<\/a><\/noindex>Zasada zawsze jest taka sama:<\/p>\n<ul>\n<li>Istnieje jaka\u015b prawda (wewn\u0119trzna lub zewn\u0119trzna) \u2013 na przyk\u0142ad specyfikacja, model danych, itd.<\/li>\n<li>Potrzebujemy lokalnego przedstawienia tej prawdy w naszym j\u0119zyku programowania.<\/li>\n<\/ul>\n<p>\nGenerowanie takiego przedstawienia jest niemal zawsze celowe \u2013 aby unikn\u0105\u0107 nadmiarowo\u015bci.<\/p>\n<h4>Dostawcy typ\u00f3w i przetwarzanie adnotacji<\/h4>\n<p>\nDo notatki: innego, bardziej nowoczesnego i specyficznego podej\u015bcia do generowania kodu dla jOOQ wi\u0105\u017ce si\u0119 z wykorzystaniem dostawc\u00f3w typ\u00f3w, <noindex><a rel=\"nofollow\" href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/fsharp\/tutorials\/type-providers\/\">w takim wydaniu, jakie realizowane jest w F#.<\/a><\/noindex>W takim przypadku kod jest generowany przez kompilator na etapie kompilacji. W formie \u017ar\u00f3de\u0142 taki kod zasadniczo nie istnieje. W Javie istniej\u0105 podobne, chocia\u017c nieco mniej eleganckie narz\u0119dzia \u2013 to procesory adnotacji, na przyk\u0142ad, <noindex><a rel=\"nofollow\" href=\"https:\/\/projectlombok.org\">Lombok.<\/a><\/noindex>.<\/p>\n<p>W pewnym sensie zachodz\u0105 tutaj te same procesy, co w pierwszym przypadku, z jednym wyj\u0105tkiem:<\/p>\n<ul>\n<li>Nie widzisz wygenerowanego kodu (mo\u017ce dla niekt\u00f3rych taka sytuacja nie wydaje si\u0119 tak nieprzyjemna?)<\/li>\n<li>Musisz zapewni\u0107, \u017ce typy mog\u0105 by\u0107 dostarczane, to znaczy, \"prawda\" musi by\u0107 zawsze dost\u0119pna. To jest \u0142atwe w przypadku Lombok, kt\u00f3ry adnotuje \"prawd\u0119\". Troch\u0119 trudniej z modelami baz danych, kt\u00f3rych dzia\u0142anie zale\u017cy od stale dost\u0119pnego aktywnego po\u0142\u0105czenia.<\/li>\n<\/ul>\n<p><\/p>\n<h4>Jaki jest problem z generowaniem kodu?<\/h4>\n<p>\nOpr\u00f3cz podchwytliwego pytania o to, w jaki spos\u00f3b lepiej uruchomi\u0107 generacj\u0119 kodu \u2013 r\u0119cznie czy automatycznie, musz\u0119 r\u00f3wnie\u017c wspomnie\u0107 o tym, \u017ce s\u0105 ludzie, kt\u00f3rzy uwa\u017caj\u0105, \u017ce generowanie kodu w og\u00f3le nie jest potrzebne. Uzasadnienie tego pogl\u0105du, z kt\u00f3rym spotka\u0142em si\u0119 najcz\u0119\u015bciej, polega na tym, \u017ce wtedy trudno skonfigurowa\u0107 proces budowy. Tak, faktycznie jest to trudne. Powstaj\u0105 dodatkowe wydatki zwi\u0105zane z infrastruktur\u0105. Je\u015bli dopiero zaczynasz pracowa\u0107 z okre\u015blonym produktem (niezale\u017cnie czy to jOOQ, JAXB, Hibernate itp.), czas po\u015bwi\u0119cony na konfiguracj\u0119 \u015brodowiska roboczego to czas, kt\u00f3ry chcia\u0142by\u015b przeznaczy\u0107 na nauk\u0119 samego API, aby p\u00f3\u017aniej m\u00f3c wydobywa\u0107 z niego warto\u015b\u0107.<\/p>\n<p>Je\u015bli koszty zwi\u0105zane ze zrozumieniem dzia\u0142ania generatora s\u0105 zbyt wysokie \u2013 to rzeczywi\u015bcie, w API nie zadbano o u\u017cyteczno\u015b\u0107 generatora (a p\u00f3\u017aniej okazuje si\u0119, \u017ce r\u00f3wnie\u017c u\u017cytkownik ma trudno\u015bci z jego konfiguracj\u0105). Wygoda u\u017cytkowania powinna by\u0107 najwy\u017cszym priorytetem dla ka\u017cdego takiego API. Ale to tylko jeden argument przeciwko generowaniu kodu. Poza tym, konieczne jest pisanie lokalnej reprezentacji wewn\u0119trznej lub zewn\u0119trznej rzeczywisto\u015bci ca\u0142kowicie r\u0119cznie.<\/p>\n<p>Wielu powie, \u017ce nie maj\u0105 czasu, aby si\u0119 tym zajmowa\u0107. Maj\u0105 napi\u0119te terminy oddania swojego Super-Produkt. P\u00f3\u017aniej jako\u015b poprawimy procesy budowy, zd\u0105\u017cymy. Odpowiem im:<\/p>\n<p><img decoding=\"async\" alt=\"Prawda przede wszystkim, czyli dlaczego system powinien by\u0107 projektowany z uwzgl\u0119dnieniem struktury bazy danych\" src=\"\/wp-content\/uploads\/2020\/04\/cd9aca1e0365fa5af28cfc72c201cb8c.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<noindex><a rel=\"nofollow\" href=\"https:\/\/www.flickr.com\/photos\/toddle_email_newsletters\/15596940251\/in\/photostream\/\">Orygina\u0142<\/a><\/noindex>, <noindex><a rel=\"nofollow\" href=\"http:\/\/audiencestack.com\/static\/blog.html\">Alan O'Rourke, Audience Stack<\/a><\/noindex> <\/p>\n<p><b>Jednak w Hibernate\/JPA tak \u0142atwo jest pisa\u0107 kod \"pod Jav\u0119\". <\/b><\/p>\n<p>Zgadza si\u0119. Dla Hibernate i jego u\u017cytkownik\u00f3w to jednocze\u015bnie b\u0142ogos\u0142awie\u0144stwo i przekle\u0144stwo. W Hibernate mo\u017cna po prostu napisa\u0107 kilka encji, tak:<\/p>\n<pre><code class=\"java\">\t@Entity\nclass Book {\n  @Id\n  int id;\n  String title;\n}<\/code><\/pre>\n<p>\nI prawie wszystko jest gotowe. Teraz zadaniem Hibernate jest generowanie skomplikowanych \"detali\" tego, w jaki spos\u00f3b ta encja b\u0119dzie definiowana w DDL twojego \"dialektu\" SQL:<\/p>\n<pre><code class=\"sql\">\tCREATE TABLE book (\n  id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY,\n  title VARCHAR(50),\n\n  CONSTRAINT pk_book PRIMARY KEY (id)\n);\n\nCREATE INDEX i_book_title ON book (title);<\/code><\/pre>\n<p>\n... i zaczynamy uruchamia\u0107 aplikacj\u0119. To naprawd\u0119 \u015bwietna opcja, aby szybko rozpocz\u0105\u0107 prac\u0119 i eksperymentowa\u0107 z r\u00f3\u017cnymi rzeczami.<\/p>\n<p>Jednak pozw\u00f3lcie, \u017ce si\u0119 przyznam.<\/p>\n<ul>\n<li>Czy Hibernate rzeczywi\u015bcie zastosuje definicj\u0119 tego nazwanego klucza podstawowego?<\/li>\n<li>Czy Hibernate utworzy indeks w TITLE? \u2013 dok\u0142adnie wiem, \u017ce b\u0119dzie nam potrzebny. <\/li>\n<li>Czy Hibernate na pewno uczyni ten klucz identyfikuj\u0105cym w specyfikacji Identity?<\/li>\n<\/ul>\n<p>\nPrawdopodobnie nie. Je\u015bli tworzysz sw\u00f3j projekt od podstaw, to zawsze wygodnie po prostu obr\u00f3ci\u0107 star\u0105 baz\u0119 danych i wygenerowa\u0107 now\u0105, jak tylko dodasz potrzebne adnotacje. W ten spos\u00f3b encja Book ostatecznie przyjmie posta\u0107: <\/p>\n<pre><code class=\"java\">\t@Entity\n@Table(name = \"book\", indexes = {\n  @Index(name = \"i_book_title\", columnList = \"title\")\n})\nclass Book {\n  @Id\n  @GeneratedValue(strategy = IDENTITY)\n  int id;\n  String title;\n}\n<\/code><\/pre>\n<p>\nSuper. Wygeneruj ponownie. Znowu, w takim przypadku na pocz\u0105tku b\u0119dzie to bardzo \u0142atwe.<\/p>\n<h4>Ale p\u00f3\u017aniej trzeba za to zap\u0142aci\u0107.<\/h4>\n<p>\nPr\u0119dzej czy p\u00f3\u017aniej trzeba wyj\u015b\u0107 na produkcj\u0119. To wtedy ten model przestanie dzia\u0142a\u0107. Poniewa\u017c: <\/p>\n<p>Na produkcji nie b\u0119dzie ju\u017c mo\u017cna w razie potrzeby obr\u00f3ci\u0107 starej bazy danych i zacz\u0105\u0107 od nowa. Twoja baza danych stanie si\u0119 dziedziczona.<\/p>\n<p>Od teraz i na zawsze b\u0119dziesz musia\u0142 pisa\u0107 <noindex><a rel=\"nofollow\" href=\"https:\/\/blog.jooq.org\/2014\/06\/25\/flyway-and-jooq-for-unbeatable-sql-development-productivity\/\">skrypty migracyjne DDL, na przyk\u0142ad przy pomocy Flyway.<\/a><\/noindex>. A co si\u0119 wtedy stanie z twoimi encjami? Mo\u017cesz je albo dostosowa\u0107 r\u0119cznie (i tym samym podwoi\u0107 sobie obj\u0119to\u015b\u0107 pracy), albo kaza\u0107 Hibernate wygenerowa\u0107 je na nowo (jakie s\u0105 szanse, \u017ce tak wygenerowane b\u0119d\u0105 odpowiada\u0107 twoim oczekiwaniom?) W ka\u017cdym razie przegrywasz.<\/p>\n<p>W ten spos\u00f3b, gdy tylko przejdziesz do produkcji, b\u0119dziesz potrzebowa\u0107 gor\u0105cych poprawek. A te musisz wprowadzi\u0107 do produkcji bardzo szybko. Poniewa\u017c nie by\u0142e\u015b przygotowany i nie zorganizowa\u0142e\u015b g\u0142adkiego przep\u0142ywu migracji dla produkcji, wszystko b\u0119dziesz dziko poprawia\u0107. A potem ju\u017c nie zd\u0105\u017cysz wszystko zrobi\u0107 poprawnie. I wini\u0107 b\u0119dziesz Hibernate, poniewa\u017c zawsze winny jest kto\u015b inny, nie ty...<\/p>\n<p>Zamiast tego, od samego pocz\u0105tku mo\u017cna by\u0142o wszystko robi\u0107 zupe\u0142nie inaczej. Na przyk\u0142ad za\u0142o\u017cy\u0107 na rower okr\u0105g\u0142e ko\u0142a.<\/p>\n<h4>Najpierw baza danych.<\/h4>\n<p>\nPrawdziwa \u201eprawda\u201d w schemacie twojej bazy danych i \u201esuwerenno\u015b\u0107\u201d nad ni\u0105 kryje si\u0119 wewn\u0105trz bazy danych. Schemat jest definiowany wy\u0142\u0105cznie w samej bazie danych i nigdzie indziej, a ka\u017cdy z klient\u00f3w ma kopi\u0119 tego schematu, dlatego ca\u0142kowicie zasadnym jest narzucenie przestrzegania schematu i jego integralno\u015bci, robienie tego bezpo\u015brednio w bazie danych \u2013 tam, gdzie przechowywane s\u0105 informacje. <br \/>\nTo stara, wr\u0119cz oklepana m\u0105dro\u015b\u0107. Klucze g\u0142\u00f3wne i unikalne to dobrze. Klucze obce \u2013 to dobrze. Weryfikacja ogranicze\u0144 \u2013 to dobrze. <noindex><a rel=\"nofollow\" href=\"https:\/\/community.oracle.com\/ideas\/13028\">Twierdzenia<\/a><\/noindex> \u2013 to dobrze. <\/p>\n<p>Co wi\u0119cej, to jeszcze nie wszystko. Na przyk\u0142ad, u\u017cywaj\u0105c Oracle, prawdopodobnie zechcesz okre\u015bli\u0107:<\/p>\n<ul>\n<li>W kt\u00f3rym przestrzeni tabel znajduje si\u0119 twoja tabela <\/li>\n<li>Jak\u0105 ma warto\u015b\u0107 PCTFREE <\/li>\n<li>Jaki jest rozmiar pami\u0119ci podr\u0119cznej w twojej sekwencji (za identyfikatorem)<\/li>\n<\/ul>\n<p>\nMo\u017ce to wszystko nie ma du\u017cego znaczenia w ma\u0142ych systemach, ale nie trzeba czeka\u0107 na przej\u015bcie do obszaru \u201ebig data\u201d \u2013 mo\u017cna znacznie wcze\u015bniej zacz\u0105\u0107 korzysta\u0107 z udoskonale\u0144 dostarczanych przez dostawc\u0119, takich jak wspomniane powy\u017cej. \u017badne ORM, kt\u00f3re mia\u0142em okazj\u0119 zobaczy\u0107 (w tym jOOQ), nie zapewniaj\u0105 dost\u0119pu do pe\u0142nego zestawu opcji DDL, kt\u00f3re by\u0107 mo\u017ce zechcesz wykorzysta\u0107 w swojej bazie danych. ORM oferuj\u0105 pewne narz\u0119dzia, kt\u00f3re pomagaj\u0105 pisa\u0107 DDL.<\/p>\n<p>Ale w ko\u0144cu dobrze zaprojektowany schemat jest r\u0119cznie napisany w DDL. Ka\u017cdy wygenerowany DDL jest jedynie jego przybli\u017ceniem.<\/p>\n<h4>A co z modelem klienta?<\/h4>\n<p>\nJak wspomniano wcze\u015bniej, po stronie klienta b\u0119dziesz potrzebowa\u0107 kopii schematu swojej bazy danych, widoku klienta. Nadmiarowo wspomina\u0107, \u017ce ten widok klienta musi by\u0107 zsynchronizowany z rzeczywistym modelem. Jak najlepiej to osi\u0105gn\u0105\u0107? Przy pomocy generatora kodu.<\/p>\n<p>Wszystkie bazy danych udost\u0119pniaj\u0105 swoj\u0105 metainformacj\u0119 przez SQL. Oto jak uzyska\u0107 wszystkie tabele z twojej bazy danych w r\u00f3\u017cnych dialektach SQL:<\/p>\n<pre><code class=\"sql\">\t-- H2, HSQLDB, MySQL, PostgreSQL, SQL Server\nSELECT table_schema, table_name\nFROM information_schema.tables\n \n-- DB2\nSELECT tabschema, tabname\nFROM syscat.tables\n \n-- Oracle\nSELECT owner, table_name\nFROM all_tables\n \n-- SQLite\nSELECT name\nFROM sqlite_master\n \n-- Teradata\nSELECT databasename, tablename\nFROM dbc.tables\n<\/code><\/pre>\n<p>\nTe zapytania (lub podobne, w zale\u017cno\u015bci od tego, czy nale\u017cy tak\u017ce uwzgl\u0119dnia\u0107 widoki, zmaterializowane widoki, funkcje z warto\u015bci\u0105 tabelaryczn\u0105) r\u00f3wnie\u017c s\u0105 realizowane za pomoc\u0105 wywo\u0142ania <noindex><a rel=\"nofollow\" href=\"https:\/\/docs.oracle.com\/javase\/10\/docs\/api\/java\/sql\/DatabaseMetaData.html#getTables(java.lang.String,java.lang.String,java.lang.String,java.lang.String%5B%5D)\"><code>DatabaseMetaData.getTables()<\/code><\/a><\/noindex> z JDBC, albo przy pomocy modu\u0142u metadanych jOOQ.<\/p>\n<p>Z wynik\u00f3w takich zapyta\u0144 stosunkowo \u0142atwo jest wygenerowa\u0107 dowolny widok modelu twojej bazy danych, niezale\u017cnie od technologii, kt\u00f3r\u0105 wykorzystujesz po stronie klienta.<\/p>\n<ul>\n<li>Je\u015bli u\u017cywasz JDBC lub Spring, mo\u017cesz stworzy\u0107 zbi\u00f3r sta\u0142ych string\u00f3w <\/li>\n<li>Je\u015bli u\u017cywasz JPA, mo\u017cesz wygenerowa\u0107 same encje <\/li>\n<li>Je\u015bli u\u017cywasz jOOQ, mo\u017cesz wygenerowa\u0107 metamodel jOOQ<\/li>\n<\/ul>\n<p>\nW zale\u017cno\u015bci od tego, jakie mo\u017cliwo\u015bci oferuje Twoje API klienckie (np. jOOQ lub JPA), wygenerowany model metadanych mo\u017ce by\u0107 naprawd\u0119 bogaty i kompletny. We\u017amy na przyk\u0142ad mo\u017cliwo\u015b\u0107 niejawnych po\u0142\u0105cze\u0144, <noindex><a rel=\"nofollow\" href=\"https:\/\/blog.jooq.org\/2018\/02\/20\/type-safe-implicit-join-through-path-navigation-in-jooq-3-11\/\">wyst\u0119puj\u0105c\u0105 w jOOQ 3.11<\/a><\/noindex>, kt\u00f3ra opiera si\u0119 na wygenerowanej metainformacji o relacjach kluczy obcych mi\u0119dzy Twoimi tabelami.<\/p>\n<p>Teraz ka\u017cde zwi\u0119kszenie bazy danych automatycznie spowoduje aktualizacj\u0119 kodu klienckiego. Wyobra\u017a sobie na przyk\u0142ad:<\/p>\n<pre><code class=\"sql\">ALTER TABLE book RENAME COLUMN title TO book_title;<\/code><\/pre>\n<p>\nCzy naprawd\u0119 chcia\u0142by\u015b wykonywa\u0107 t\u0119 prac\u0119 dwa razy? Absolutnie nie. Po prostu rejestrujemy DDL, uruchamiamy go przez Tw\u00f3j pipeline budowania i otrzymujemy zaktualizowan\u0105 encj\u0119:<\/p>\n<pre><code class=\"java\">@Entity\n@Table(name = \"book\", indexes = {\n \n  \/\/ Czy kiedykolwiek si\u0119 nad tym zastanawia\u0142e\u015b?\n  @Index(name = \"i_book_title\", columnList = \"book_title\")\n})\nclass Book {\n  @Id\n  @GeneratedValue(strategy = IDENTITY)\n  int id;\n \n  @Column(\"book_title\")\n  String bookTitle;\n}<\/code><\/pre>\n<p>\nLub zaktualizowan\u0105 klas\u0119 jOOQ. Wi\u0119kszo\u015b\u0107 zmian DDL wp\u0142ywa tak\u017ce na semantyk\u0119, a nie tylko na sk\u0142adni\u0119. Dlatego pomocne mo\u017ce by\u0107 spojrzenie na skompilowany kod, aby zobaczy\u0107, jaki kod b\u0119dzie (lub mo\u017ce by\u0107) dotkni\u0119ty zwi\u0119kszeniem Twojej bazy danych.<\/p>\n<h4>Jedyna prawda<\/h4>\n<p>\nBez wzgl\u0119du na to, jakiej technologii u\u017cywasz, zawsze istnieje jeden model, kt\u00f3ry jest jedynym \u017ar\u00f3d\u0142em prawdy dla danej podsystemu \u2013 a przynajmniej powinni\u015bmy do tego d\u0105\u017cy\u0107 i unika\u0107 takiego zbioru danych, gdzie \u201eprawda\u201d jest jednocze\u015bnie wsz\u0119dzie i nigdzie. Mo\u017ce by\u0107 znacznie \u0142atwiej. Je\u015bli tylko wymieniasz pliki XML z jakim\u015b innym systemem, po prostu u\u017cywaj XSD. Zobacz na model metadanych INFORMATION_SCHEMA z jOOQ w formacie XML:<br \/>\n<noindex><a rel=\"nofollow\" href=\"https:\/\/www.jooq.org\/xsd\/jooq-meta-3.10.0.xsd\">https:\/\/www.jooq.org\/xsd\/jooq-meta-3.10.0.xsd<\/a><\/noindex><\/p>\n<ul>\n<li>XSD jest bardzo zrozumia\u0142a<\/li>\n<li>XSD bardzo dobrze oznacza zawarto\u015b\u0107 XML i umo\u017cliwia walidacj\u0119 we wszystkich j\u0119zykach klienckich <\/li>\n<li>XSD dobrze wersjonuje i ma rozwini\u0119t\u0105 kompatybilno\u015b\u0107 wstecz <\/li>\n<li>XSD mo\u017cna przekszta\u0142ca\u0107 w kod Java za pomoc\u0105 XJC<\/li>\n<\/ul>\n<p>\nOstatni punkt jest wa\u017cny. Przy komunikacji z zewn\u0119trznym systemem za pomoc\u0105 wiadomo\u015bci XML chcemy mie\u0107 pewno\u015b\u0107 co do wa\u017cno\u015bci naszych wiadomo\u015bci. Jest to bardzo \u0142atwe do osi\u0105gni\u0119cia przy u\u017cyciu JAXB, XJC i XSD. By\u0142oby absolutnym szale\u0144stwem zak\u0142ada\u0107, \u017ce przy podej\u015bciu do projektowania \u00abnajpierw Java\u00bb, w kt\u00f3rym tworzymy nasze wiadomo\u015bci jako obiekty Java, mo\u017cna je jako\u015b rozs\u0105dnie odwzorowa\u0107 na XML i wys\u0142a\u0107 do innego systemu. XML wygenerowany w ten spos\u00f3b by\u0142by bardzo niskiej jako\u015bci, nieudokumentowany i trudny do rozwijania. Gdyby przy takim interfejsie istnia\u0142a umowa o poziomie us\u0142ug (SLA), od razu by\u015bmy j\u0105 zepsuli.<\/p>\n<p>Szczerze m\u00f3wi\u0105c, dok\u0142adnie to ci\u0105gle dzieje si\u0119 z API w JSON, ale to ju\u017c inna historia, nast\u0119pnym razem si\u0119 do tego przyczepi\u0119... <\/p>\n<h4>Bazy danych: to to samo<\/h4>\n<p>\nPracuj\u0105c z bazami danych, rozumiesz, \u017ce wszystkie one s\u0105 w zasadzie podobne. Baza danych posiada swoje dane i powinna zarz\u0105dza\u0107 schematem. Wszelkie zmiany w schemacie musz\u0105 by\u0107 realizowane bezpo\u015brednio w DDL, aby zaktualizowa\u0107 jeden \u017ar\u00f3d\u0142o prawdy.<\/p>\n<p>Kiedy aktualizacja \u017ar\u00f3d\u0142a nast\u0105pi, wszyscy klienci r\u00f3wnie\u017c musz\u0105 zaktualizowa\u0107 swoje kopie modelu. Niekt\u00f3rzy klienci mog\u0105 by\u0107 napisani w Javie z u\u017cyciem jOOQ i Hibernate lub JDBC (lub wszystkiego naraz). Inne klienci mog\u0105 by\u0107 napisani w Perl (pozostaje tylko \u017cyczy\u0107 im powodzenia), a jeszcze inne w C#. To nie ma znaczenia. G\u0142\u00f3wny model znajduje si\u0119 w bazie danych. Modele generowane przy u\u017cyciu ORM s\u0105 zazwyczaj niskiej jako\u015bci, s\u0142abo udokumentowane i trudne w rozwijaniu.<\/p>\n<p>Dlatego nie pope\u0142niaj b\u0142\u0119d\u00f3w. Od samego pocz\u0105tku nie pope\u0142niaj b\u0142\u0119d\u00f3w. Pracuj z za\u0142o\u017ceniem bazy danych. Zbuduj taki pipeline wdro\u017ceniowy, kt\u00f3ry mo\u017cna zautomatyzowa\u0107. W\u0142\u0105cz generatory kodu, aby by\u0142o wygodnie kopiowa\u0107 model swojej bazy danych i przekazywa\u0107 go do klient\u00f3w. I przesta\u0144 martwi\u0107 si\u0119 o generatory kodu. S\u0105 dobre. Dzi\u0119ki nim staniesz si\u0119 bardziej produktywny. Wystarczy tylko na pocz\u0105tku po\u015bwi\u0119ci\u0107 troch\u0119 czasu na ich konfiguracj\u0119 \u2013 a potem czeka na Ciebie lata zwi\u0119kszonej wydajno\u015bci, z kt\u00f3rych ukszta\u0142tuje si\u0119 historia Twojego projektu.<\/p>\n<p>Na razie nie dzi\u0119kuj, p\u00f3\u017aniej.<\/p>\n<h4>Wyja\u015bnienie<\/h4>\n<p>\nDla jasno\u015bci: Ten artyku\u0142 w \u017cadnym wypadku nie propaguje, \u017ce model bazy danych powinien zdominowa\u0107 ca\u0142y system (tj. obszar tematyczny, logika biznesowa itd.). W tym artykule m\u00f3wi\u0119 o tym, \u017ce kod kliencki, kt\u00f3ry wsp\u00f3\u0142dzia\u0142a z baz\u0105 danych, powinien dzia\u0142a\u0107 w oparciu o model bazy danych, tak, aby sam nie reprodukowa\u0142 modelu bazy danych w statusie \u201epierwszej klasy\u201d. Taka logika zwykle znajduje si\u0119 na poziomie dost\u0119pu do danych po stronie klienta.<\/p>\n<p>W architekturach dwupoziomowych, kt\u00f3re wci\u0105\u017c gdzie\u015b funkcjonuj\u0105, taki model systemu mo\u017ce by\u0107 jedynym mo\u017cliwym. Jednak w wi\u0119kszo\u015bci system\u00f3w poziom dost\u0119pu do danych wydaje mi si\u0119 \u201epodsystemem\u201d, inkapsuluj\u0105cym model bazy danych.<\/p>\n<h4>Wyj\u0105tki<\/h4>\n<p>\nZ ka\u017cdego przepisu s\u0105 wyj\u0105tki, i ju\u017c m\u00f3wi\u0142em, \u017ce podej\u015bcie z prymat\u0105 bazy danych i generowaniem kodu \u017ar\u00f3d\u0142owego mo\u017ce czasami by\u0107 niew\u0142a\u015bciwe. Oto kilka takich wyj\u0105tk\u00f3w (prawdopodobnie znajd\u0105 si\u0119 tak\u017ce inne):<\/p>\n<ul>\n<li>Kiedy schemat jest nieznany i nale\u017cy go odkry\u0107. Na przyk\u0142ad, jeste\u015b dostawc\u0105 narz\u0119dzi, kt\u00f3re pomagaj\u0105 u\u017cytkownikom orientowa\u0107 si\u0119 w dowolnym schemacie. Uff. Tutaj trudno bez generacji kodu. Ale wci\u0105\u017c \u2013 baza danych przede wszystkim.<\/li>\n<li>Kiedy schemat musi by\u0107 generowany na bie\u017c\u0105co w celu rozwi\u0105zania pewnego zadania. Ten przyk\u0142ad wydaje si\u0119 nieco wyrafinowan\u0105 wersj\u0105 wzorca <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Entity%E2%80%93attribute%E2%80%93value_model\">entity attribute value<\/a><\/noindex>tj. rzeczywi\u015bcie nie masz \u015bci\u015ble okre\u015blonego schematu. W takim przypadku cz\u0119sto nie mo\u017cna nawet by\u0107 pewnym, czy nadaje si\u0119 do u\u017cywania RDBMS.<\/li>\n<\/ul>\n<p>\nWyj\u0105tki z definicji s\u0105 wyj\u0105tkowe. W wi\u0119kszo\u015bci przypadk\u00f3w zwi\u0105zanych z u\u017cywaniem RDBMS schemat jest znany z g\u00f3ry, znajduje si\u0119 wewn\u0105trz RDBMS i jest jedynym \u017ar\u00f3d\u0142em \u201eprawdy\u201d, a wszyscy klienci musz\u0105 zaopatrywa\u0107 si\u0119 w jego pochodne. W idealnym przypadku powinno si\u0119 wtedy u\u017cywa\u0107 generatora kodu.<br \/>\n<br \/>\u0179r\u00f3d\u0142o: <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/piter\/blog\/498816\/\">habr.com<\/a> <\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u041f\u0440\u0438\u0432\u0435\u0442, \u0425\u0430\u0431\u0440! \u041c\u044b \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0430\u0435\u043c \u0438\u0441\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u044c \u0442\u0435\u043c\u0443 Java \u0438 Spring, \u0432 \u0442\u043e\u043c \u0447\u0438\u0441\u043b\u0435, \u043d\u0430 \u0443\u0440\u043e\u0432\u043d\u0435 \u0431\u0430\u0437 \u0434\u0430\u043d\u043d\u044b\u0445. \u0421\u0435\u0433\u043e\u0434\u043d\u044f \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u0435\u043c \u043f\u043e\u0447\u0438\u0442\u0430\u0442\u044c \u043e \u0442\u043e\u043c, \u043f\u043e\u0447\u0435\u043c\u0443 \u043f\u0440\u0438 \u043f\u0440\u043e\u0435\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438 \u0431\u043e\u043b\u044c\u0448\u0438\u0445 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439 \u0438\u043c\u0435\u043d\u043d\u043e \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445, \u0430 \u043d\u0435 \u043a\u043e\u0434 Java, \u0434\u043e\u043b\u0436\u043d\u0430 \u0438\u043c\u0435\u0442\u044c \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u044e\u0449\u0435\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435, \u043a\u0430\u043a \u044d\u0442\u043e \u0434\u0435\u043b\u0430\u0435\u0442\u0441\u044f, \u0438 \u043a\u0430\u043a\u0438\u0435 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0435\u0441\u0442\u044c \u0438\u0437 \u044d\u0442\u043e\u0433\u043e \u043f\u0440\u0430\u0432\u0438\u043b\u0430. \u0412 \u044d\u0442\u043e\u0439 \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u0437\u0430\u043f\u043e\u0437\u0434\u0430\u043b\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u043e\u0431\u044a\u044f\u0441\u043d\u044e, [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":79466,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-79465","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-administrirovanie"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.1.1 - aioseo.com -->\n\t<meta name=\"description\" content=\"\u041f\u0440\u0438\u0432\u0435\u0442, \u0425\u0430\u0431\u0440! \u041c\u044b \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0430\u0435\u043c \u0438\u0441\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u044c \u0442\u0435\u043c\u0443\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Yuri Gagarin\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/istina-prezhde-vsego-ili-pochemu-sistemu-nuzhno-proektirovat-ishodya-iz-ustrojstva-bazy-dannyh\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.1.1\" \/>\n\t\t<meta property=\"og:locale\" content=\"pl_PL\" \/>\n\t\t<meta property=\"og:site_name\" content=\"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"\ud83e\udd47\u0418\u0441\u0442\u0438\u043d\u0430 \u043f\u0440\u0435\u0436\u0434\u0435 \u0432\u0441\u0435\u0433\u043e, \u0438\u043b\u0438 \u043f\u043e\u0447\u0435\u043c\u0443 \u0441\u0438\u0441\u0442\u0435\u043c\u0443 \u043d\u0443\u0436\u043d\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c, \u0438\u0441\u0445\u043e\u0434\u044f \u0438\u0437 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445 | ProHoster\" \/>\n\t\t<meta property=\"og:description\" content=\"\u041f\u0440\u0438\u0432\u0435\u0442, \u0425\u0430\u0431\u0440! \u041c\u044b \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0430\u0435\u043c \u0438\u0441\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u044c \u0442\u0435\u043c\u0443\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/istina-prezhde-vsego-ili-pochemu-sistemu-nuzhno-proektirovat-ishodya-iz-ustrojstva-bazy-dannyh\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:width\" content=\"350\" \/>\n\t\t<meta property=\"og:image:height\" content=\"350\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2020-04-27T05:42:20+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2020-04-27T05:42:20+00:00\" \/>\n\t\t<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"\ud83e\udd47Prawda przede wszystkim, czyli dlaczego system nale\u017cy projektowa\u0107 w oparciu o struktur\u0119 bazy danych | ProHoster","description":"Cze\u015b\u0107, Habr! Kontynuujemy badanie tematu","canonical_url":"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/istina-prezhde-vsego-ili-pochemu-sistemu-nuzhno-proektirovat-ishodya-iz-ustrojstva-bazy-dannyh","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"pl_PL","og:site_name":"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b","og:type":"article","og:title":"\ud83e\udd47\u0418\u0441\u0442\u0438\u043d\u0430 \u043f\u0440\u0435\u0436\u0434\u0435 \u0432\u0441\u0435\u0433\u043e, \u0438\u043b\u0438 \u043f\u043e\u0447\u0435\u043c\u0443 \u0441\u0438\u0441\u0442\u0435\u043c\u0443 \u043d\u0443\u0436\u043d\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c, \u0438\u0441\u0445\u043e\u0434\u044f \u0438\u0437 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445 | ProHoster","og:description":"\u041f\u0440\u0438\u0432\u0435\u0442, \u0425\u0430\u0431\u0440! \u041c\u044b \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0430\u0435\u043c \u0438\u0441\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u044c \u0442\u0435\u043c\u0443","og:url":"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/istina-prezhde-vsego-ili-pochemu-sistemu-nuzhno-proektirovat-ishodya-iz-ustrojstva-bazy-dannyh","og:image":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:secure_url":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:width":350,"og:image:height":350,"article:published_time":"2020-04-27T05:42:20+00:00","article:modified_time":"2020-04-27T05:42:20+00:00","article:publisher":"https:\/\/www.facebook.com\/prohoster","article:author":"https:\/\/www.facebook.com\/prohoster"},"aioseo_meta_data":{"post_id":"79465","title":null,"description":null,"keywords":null,"keyphrases":null,"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"","isEnabled":true},"graphs":[]},"schema_type":null,"schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"local_seo":null,"seo_analyzer_scan_date":null,"breadcrumb_settings":null,"limit_modified_date":false,"reviewed_by":null,"ai":null,"created":"2021-02-28 16:37:24","updated":"2022-09-29 16:13:15","focus_keyword":null,"additional_keywords":null,"truseo_locale":null},"gt_translate_keys":[{"key":"link","format":"url"}],"_links":{"self":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/posts\/79465","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/comments?post=79465"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/posts\/79465\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/media\/79466"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/media?parent=79465"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/categories?post=79465"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/tags?post=79465"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}