Përvoja «Database as Code»

Përvoja «Database as Code»

SQL — çfarĂ« mund tĂ« jetĂ« mĂ« e thjeshtĂ«? Secili prej nesh mund tĂ« shkruajĂ« njĂ« query tĂ« thjeshtĂ« — shkruajmĂ« select, rendisim kolonat e nevojshme, pastaj from, emrin e tabelĂ«s, pak kushte te ku dhe kaq — tĂ« dhĂ«nat e dobishme i kemi nĂ« xhep, thuajse pavarĂ«sisht se cila DBMS fshihet pas skenĂ«s nĂ« atĂ« moment (madje ndoshta as nuk Ă«shtĂ« fare DBMS). Si rezultat, puna pothuajse me çdo burim tĂ« dhĂ«nash (relacional apo jo fort i tillĂ«) mund tĂ« shihet nga kĂ«ndvĂ«shtrimi i kodit tĂ« zakonshĂ«m (me gjithçka qĂ« sjell kjo — version control, code review, analizĂ« statike, autoteste e tĂ« tjera). Dhe kjo vlen jo vetĂ«m pĂ«r vetĂ« tĂ« dhĂ«nat, skemat dhe migrimet, por nĂ« pĂ«rgjithĂ«si pĂ«r tĂ« gjithĂ« ciklin e jetĂ«s sĂ« ruajtjes sĂ« tĂ« dhĂ«nave. NĂ« kĂ«tĂ« artikull do tĂ« flasim pĂ«r detyrat dhe problemet e pĂ«rditshme gjatĂ« punĂ«s me DB tĂ« ndryshme nĂ«n prizmin e "database as code".

Dhe do ta nisim pikërisht me ORM. Përplasjet e para të tipit "SQL vs ORM" janë vënë re që në kohë shumë të hershme.

Mapimi objekt-relacional

Mbështetësit e ORM tradicionalisht vlerësojnë shpejtësinë dhe thjeshtësinë e zhvillimit, pavarësinë nga DBMS dhe pastërtinë e kodit. Për shumë prej nesh, kodi për punën me DB (dhe shpesh edhe vetë DB)

zakonisht duket afërsisht kështu


@Entity
@Table(name = "stock", catalog = "maindb", uniqueConstraints = {
        @UniqueConstraint(columnNames = "STOCK_NAME"),
        @UniqueConstraint(columnNames = "STOCK_CODE") })
public class Stock implements java.io.Serializable {

    @Id
    @GeneratedValue(strategy = IDENTITY)
    @Column(name = "STOCK_ID", unique = true, nullable = false)
    public Integer getStockId() {
        return this.stockId;
    }
  ...

Modeli është i mbushur me anotime inteligjente, ndërsa diku prapa skenës ORM-ja trimërore gjeneron dhe ekzekuton tonelata kodi SQL. Meqë ra fjala, zhvilluesit përpiqen me të gjitha forcat të distancohen nga DB-ja e tyre përmes kilometrash abstraksionesh, gjë që flet për njëfarë "urrejtjeje ndaj SQL".

NĂ« anĂ«n tjetĂ«r tĂ« barrikadĂ«s, ithtarĂ«t e SQL-sĂ« sĂ« pastĂ«r "handmade" vĂ«nĂ« nĂ« dukje mundĂ«sinĂ« pĂ«r t’i nxjerrĂ« maksimumin DBMS-sĂ« sĂ« tyre pa shtresa dhe abstraksione shtesĂ«. Si rezultat lindin projekte "data-centric", ku me bazĂ«n merren njerĂ«z tĂ« specializuar posaçërisht (tĂ« ashtuquajturit "bazistĂ«", "specialistĂ« tĂ« bazĂ«s" e kĂ«shtu me radhĂ«), ndĂ«rsa zhvilluesve u mbetet vetĂ«m tĂ« "thĂ«rrasin" view-t e gatshme dhe stored procedure, pa hyrĂ« nĂ« hollĂ«si.

Po sikur të marrim më të mirën nga të dy botët? Pikërisht siç është bërë në mjetin e shkëlqyer me emrin optimist Yesql. Do të sjell disa rreshta nga koncepti i përgjithshëm në përkthimin tim të lirë, ndërsa me të mund të njiheni më në hollësi këtu.

Clojure është një gjuhë e shkëlqyer për krijimin e DSL-ve, por SQL tashmë është vetë një DSL i shkëlqyer dhe nuk na duhet edhe një tjetër. S-shprehjet janë të mrekullueshme, por këtu nuk sjellin asgjë të re. Në fund marrim thjesht kllapa për hir të kllapave. Nuk jeni dakord? Atëherë prisni momentin kur abstraksioni mbi DB do të fillojë të rrjedhë dhe ju do të nisni betejën me funksionin (raw-sql)

Po çfarĂ« tĂ« bĂ«jmĂ«? Le ta lĂ«mĂ« SQL-in tĂ« mbetet SQL i zakonshĂ«m — njĂ« skedar pĂ«r njĂ« kĂ«rkesĂ«:

-- name: users-by-country
select *
  from users
 where country_code = :country_code


 dhe më pas lexoni këtë skedar, duke e kthyer në një funksion të zakonshëm Clojure:

(defqueries "some/where/users_by_country.sql"
   {:connection db-spec})

;;; A function with the name `users-by-country` has been created.
;;; Let's use it:
(users-by-country {:country_code "GB"})
;=> ({:name "Kris" :country_code "GB" ...} ...)

Duke iu përmbajtur parimit "SQL veçmas, Clojure veçmas", ju përfitoni:

  • AsnjĂ« surprizĂ« sintaksore. Baza juaj e tĂ« dhĂ«nave (si çdo tjetĂ«r) nuk pĂ«rputhet 100% me standardin SQL — por pĂ«r Yesql kjo nuk ka rĂ«ndĂ«si. Nuk do tĂ« humbni kurrĂ« kohĂ« duke kĂ«rkuar funksione me sintaksĂ« ekuivalente me SQL. Nuk do t’ju duhet kurrĂ« tĂ« ktheheni te funksioni (raw-sql "some (‘funky’ :: SYNTAX)")).
  • MbĂ«shtetje mĂ« e mirĂ« nga editori. Editori juaj tashmĂ« ka mbĂ«shtetje tĂ« shkĂ«lqyer pĂ«r SQL. Duke e ruajtur SQL-in si SQL, thjesht mund ta pĂ«rdorni atĂ«.
  • PĂ«rputhshmĂ«ri mĂ« e mirĂ« me ekipin. DBA-tĂ« tuaj mund tĂ« lexojnĂ« dhe tĂ« shkruajnĂ« SQL-in qĂ« pĂ«rdorni nĂ« projektin tuaj Clojure.
  • Konfigurim mĂ« i thjeshtĂ« i performancĂ«s. Duhet tĂ« ndĂ«rtoni planin pĂ«r njĂ« kĂ«rkesĂ« problematike? Nuk Ă«shtĂ« problem, kur kĂ«rkesa juaj Ă«shtĂ« SQL i zakonshĂ«m.
  • RipĂ«rdorim i kĂ«rkesave. Merrini kĂ«ta skedarĂ« SQL dhe pĂ«rdorini nĂ« projekte tĂ« tjera, sepse Ă«shtĂ« thjesht SQL i vjetĂ«r i mirĂ« — mjafton ta ndani.

Mendoj se ideja është shumë e bukur dhe njëkohësisht shumë e thjeshtë, prandaj projekti ka fituar shumë ndjekësve në gjuhë nga më të ndryshmet. Ndërsa më tej do të përpiqemi të zbatojmë një filozofi të ngjashme të ndarjes së kodit SQL nga gjithçka tjetër, shumë përtej ORM.

IDE & DB-menaxherë

Le të nisim me një detyrë të thjeshtë të përditshme. Shpesh na duhet të kërkojmë objekte të ndryshme në DB, për shembull të gjejmë një tabelë në një skemë dhe të shqyrtojmë strukturën e saj (cilat kolona, çelësa, indekse, constraints dhe elemente të tjera përdoren). Dhe nga çdo IDE grafike ose një DB-manager sado modest, para së gjithash presim pikërisht këto mundësi. Që gjithçka të jetë e shpejtë dhe të mos duhet të presim gjysmë ore derisa të hapet dritarja me informacionin e nevojshëm (sidomos me një lidhje të ngadaltë me DB në distancë), dhe njëkohësisht që informacioni i marrë të jetë i freskët dhe aktual, jo një cache e vjetruar. Madje, sa më komplekse e më e madhe të jetë DB dhe sa më i lartë të jetë numri i tyre, aq më e vështirë bëhet kjo.

Por zakonisht e lë mënjanë miun dhe thjesht shkruaj kod. Supozojmë se duhet të mësojmë se cilat tabela (dhe me çfarë vetish) ndodhen në skemën "HR". Në shumicën e DBMS-ve, rezultati i nevojshëm mund të merret me një query të thjeshtë nga information_schema:

select table_name
     , ...
  from information_schema.tables
 where schema = 'HR'

Nga një bazë te tjetra, përmbajtja e këtyre tabelave referuese ndryshon në varësi të mundësive të secilit DBMS. Për shembull, në MySQL nga i njëjti katalog mund të merren parametrat e tabelës specifikë për këtë DBMS:

select table_name
     , storage_engine -- "Engine" i përdorur ("MyISAM", "InnoDB" etc)
     , row_format     -- Formati i rreshtit ("Fixed", "Dynamic" etc)
     , ...
  from information_schema.tables
 where schema = 'HR'

Oracle nuk e mbështet information_schema, por ka Oracle metadata, ndaj nuk lindin probleme të mëdha:

select table_name
     , pct_free       -- Minimumi i hapësirës së lirë në bllokun e të dhënave (%)
     , pct_used       -- Minimumi i hapësirës së përdorur në bllokun e të dhënave (%)
     , last_analyzed  -- Data e mbledhjes së fundit të statistikave
     , ...
  from all_tables
 where owner = 'HR'

As ClickHouse nuk bën përjashtim:

select name
     , engine -- "Engine" i përdorur ("MergeTree", "Dictionary" etc)
     , ...
  from system.tables
 where database = 'HR'

Diçka e ngjashme mund të bëhet edhe në Cassandra (ku ka columnfamilies në vend të tables dhe keyspace në vend të skemave):

select columnfamily_name
     , compaction_strategy_class  -- Strategjia e grumbullimit të mbeturinave
     , gc_grace_seconds           -- Koha e jetës së mbeturinave
     , ...
  from system.schema_columnfamilies
 where keyspace_name = 'HR'

Edhe për shumicën e DB-ve të tjera mund të krijohen query të ngjashme (madje edhe Mongo ka koleksion të posaçëm sistemor, i cili përmban informacion për të gjitha koleksionet në sistem).

Natyrisht, nĂ« kĂ«tĂ« mĂ«nyrĂ« mund tĂ« merrni informacion jo vetĂ«m pĂ«r tabelat, por nĂ« pĂ«rgjithĂ«si pĂ«r çdo objekt. HerĂ« pas here, njerĂ«z tĂ« mirĂ« ndajnĂ« njĂ« kod tĂ« tillĂ« pĂ«r DB tĂ« ndryshme, si pĂ«r shembull nĂ« serinĂ« e artikujve nĂ« Habr "Funksione pĂ«r dokumentimin e bazave tĂ« tĂ« dhĂ«nave PostgreSQL" (ib, ben, gim). Sigurisht, ta mbash nĂ« mendje gjithĂ« kĂ«tĂ« mal me kĂ«rkesa dhe t’i shtypĂ«sh vazhdimisht Ă«shtĂ« njĂ« "kĂ«naqĂ«si" jo fort e madhe, ndaj nĂ« IDE/redaktorin tim tĂ« preferuar kam njĂ« grup snippets tĂ« pĂ«rgatitura paraprakisht pĂ«r kĂ«rkesat qĂ« pĂ«rdor mĂ« shpesh, dhe mbetet vetĂ«m tĂ« shkruaj emrat e objekteve nĂ« shabllon.

Në fund, kjo mënyrë navigimi dhe kërkimi të objekteve është shumë më fleksibël, kursen shumë kohë dhe ju lejon të merrni pikërisht atë informacion dhe në atë formë që ju nevojitet në atë moment (siç përshkruhet, për shembull, në postimin "Eksportimi i të dhënave nga DB në çdo format: çfarë dinë të bëjnë IDE në platformën IntelliJ").

Veprime me objektet

Pasi kemi gjetur dhe shqyrtuar objektet e nevojshme, është koha të bëjmë me to diçka të dobishme. Natyrisht, pa i hequr duart nga tastiera.

Nuk është sekret që fshirja e thjeshtë e një tabele do të duket pothuajse njësoj në pothuajse të gjitha DB:

drop table hr.persons

NdĂ«rsa me krijimin e njĂ« tabele bĂ«het mĂ« interesante. Praktikisht çdo DBMS (pĂ«rfshirĂ« edhe shumĂ« NoSQL) nĂ« njĂ« formĂ« apo nĂ« njĂ« tjetĂ«r mbĂ«shtet "create table", dhe pjesa kryesore e saj ndryshon shumĂ« pak (emri, lista e kolonave, llojet e tĂ« dhĂ«nave), por detajet e tjera mund tĂ« ndryshojnĂ« rrĂ«njĂ«sisht dhe varen nga arkitektura e brendshme dhe mundĂ«sitĂ« e DBMS-sĂ« konkrete. Shembulli im i preferuar Ă«shtĂ« se nĂ« dokumentacionin e Oracle vetĂ«m BNF-tĂ« "e zhveshura" pĂ«r sintaksĂ«n e "create table" zĂ«nĂ« 31 faqe. DBMS tĂ« tjera kanĂ« mundĂ«si mĂ« modeste, por secila prej tyre gjithashtu ofron shumĂ« veçori interesante dhe unike pĂ«r krijimin e tabelave (postgres, mysql, cockroach, cassandra). VĂ«shtirĂ« se ndonjĂ« "wizard" grafik nga ndonjĂ« IDE tjetĂ«r (sidomos universale) do tĂ« mund t’i mbulojĂ« plotĂ«sisht tĂ« gjitha kĂ«to mundĂ«si, dhe edhe nĂ«se do tĂ« mundej, nuk do tĂ« ishte pamje pĂ«r ata qĂ« e kanĂ« zemrĂ«n e dobĂ«t. Nga ana tjetĂ«r, njĂ« komandĂ« create table e shkruar saktĂ« dhe nĂ« kohĂ«n e duhur do t’ju lejojĂ« t’i shfrytĂ«zoni pa vĂ«shtirĂ«si tĂ« gjitha kĂ«to, duke e bĂ«rĂ« ruajtjen dhe qasjen nĂ« tĂ« dhĂ«nat tuaja tĂ« besueshme, optimale dhe sa mĂ« komode.

Gjithashtu, nĂ« shumĂ« DBMS ka lloje specifike objektesh qĂ« mungojnĂ« nĂ« DBMS tĂ« tjera. Madje mund tĂ« kryejmĂ« operacione jo vetĂ«m mbi objektet e bazĂ«s sĂ« tĂ« dhĂ«nave, por edhe mbi vetĂ« DBMS-nĂ«, pĂ«r shembull tĂ« “vrasim” njĂ« proces, tĂ« lirojmĂ« njĂ« zonĂ« tĂ« caktuar tĂ« memories, tĂ« aktivizojmĂ« gjurmimin, tĂ« kalojmĂ« nĂ« modalitetin “read only” dhe shumĂ« tĂ« tjera.

Dhe tani të vizatojmë pak

NjĂ« nga detyrat mĂ« tĂ« zakonshme Ă«shtĂ« ndĂ«rtimi i njĂ« diagrami me objektet e bazĂ«s sĂ« tĂ« dhĂ«nave, qĂ« nĂ« njĂ« pamje tĂ« qartĂ« tĂ« shihen objektet dhe lidhjet mes tyre. KĂ«tĂ« e mbĂ«shtet pothuajse çdo IDE grafike, utilitete tĂ« veçanta tĂ« “command line”, mjete grafike tĂ« specializuara dhe modelues. Ato do t’ju vizatojnĂ« diçka “siç dinĂ«â€, ndĂ«rsa ndikimi mbi kĂ«tĂ« proces zakonisht kufizohet nĂ« disa parametra nĂ« skedarin e konfigurimit ose disa opsione nĂ« ndĂ«rfaqe.

Por kjo çështje mund tĂ« zgjidhet shumĂ« mĂ« thjesht, mĂ« fleksibĂ«l dhe mĂ« elegant, dhe sigurisht me ndihmĂ«n e kodit. PĂ«r ndĂ«rtimin e diagrameve tĂ« çdo kompleksiteti kemi disa gjuhĂ« tĂ« specializuara markimi (DOT, GraphML etc), si dhe njĂ« gamĂ« tĂ« tĂ«rĂ« aplikacionesh (GraphViz, PlantUML, Mermaid), qĂ« dinĂ« tĂ« lexojnĂ« kĂ«to udhĂ«zime dhe t’i vizualizojnĂ« nĂ« formate tĂ« ndryshme. NdĂ«rsa informacionin pĂ«r objektet dhe lidhjet mes tyre tashmĂ« e dimĂ« si ta marrim.

Le të japim një shembull të shkurtër se si mund të dukej kjo, duke përdorur PlantUML dhe bazën demonstrative të të dhënave për PostgreSQL (majtas është SQL-kërkesa që gjeneron udhëzimin e nevojshëm për PlantUML, ndërsa djathtas rezultati):

Përvoja «Database as Code»

select '@startuml'||chr(10)||'hide methods'||chr(10)||'hide stereotypes' union all
select distinct ccu.table_name || ' --|> ' ||
       tc.table_name as val
  from table_constraints as tc
  join key_column_usage as kcu
    on tc.constraint_name = kcu.constraint_name
  join constraint_column_usage as ccu
    on ccu.constraint_name = tc.constraint_name
 where tc.constraint_type = 'FOREIGN KEY'
   and tc.table_name ~ '.*' union all
select '@enduml'

Dhe nëse bëjmë pak më shumë përpjekje, atëherë mbi bazën e shabllonit ER për PlantUML mund të marrim diçka shumë të ngjashme me një diagramë të vërtetë ER:

Një SQL-kërkesë paksa më e ndërlikuar

-- Koka
select '@startuml
        !define Table(name,desc) class name as "desc" << (T,#FFAAAA) >&gt;
        !define primary_key(x) <b>x</b>
        !define unique(x) <color:green>x</color>
        !define not_null(x) <u>x</u>
        hide methods
        hide stereotypes'
 union all
-- Tabelat
select format('Table(%s, "%s n informacion rreth %s") {'||chr(10), table_name, table_name, table_name) ||
       (select string_agg(column_name || ' ' || upper(udt_name), chr(10))
          from information_schema.columns
         where table_schema = 'public'
           and table_name = t.table_name) || chr(10) || '}'
  from information_schema.tables t
 where table_schema = 'public'
 union all
-- Marrëdhëniet midis tabelave
select distinct ccu.table_name || ' "1" --&gt; "0..N" ' || tc.table_name || format(' : "Një %s mund të ketë shumë %s"', ccu.table_name, tc.table_name)
  from information_schema.table_constraints as tc
  join information_schema.key_column_usage as kcu on tc.constraint_name = kcu.constraint_name
  join information_schema.constraint_column_usage as ccu on ccu.constraint_name = tc.constraint_name
 where tc.constraint_type = 'FOREIGN KEY'
   and ccu.constraint_schema = 'public'
   and tc.table_name ~ '.*'
 union all
-- Fundi
select '@enduml'

Përvoja «Database as Code»

NĂ«se e shikoni me vĂ«mendje, shumĂ« mjete vizualizimi nĂ«n kapak pĂ«rdorin pikĂ«risht kĂ«rkesa tĂ« ngjashme. VĂ«rtet, kĂ«to kĂ«rkesa zakonisht janĂ« thellĂ« “tĂ« ngulitura” nĂ« kodin e vetĂ« aplikacionit dhe janĂ« tĂ« vĂ«shtira pĂ«r t’u kuptuar, pa folur fare pĂ«r ndonjĂ« modifikim tĂ« tyre.

Metrikat dhe monitorimi

Le tĂ« kalojmĂ« te njĂ« temĂ« tradicionalisht e vĂ«shtirĂ« — monitorimi i performancĂ«s sĂ« DB. MĂ« kujtohet njĂ« histori e vogĂ«l e vĂ«rtetĂ«, qĂ« ma tregoi “njĂ« miku im”. NĂ« njĂ« nga projektet ishte njĂ« DBA shumĂ« i fuqishĂ«m dhe pak zhvillues e njihnin personalisht, madje rrallĂ« kush e kishte parĂ« ndonjĂ«herĂ« me sy (edhe pse, sipas thashethemeve, punonte diku nĂ« godinĂ«n ngjitur). NĂ« orĂ«n “X”, kur sistemi production i njĂ« retaileri tĂ« madh niste sĂ«rish “tĂ« mos ndihej mirĂ«â€, ai dĂ«rgonte nĂ« heshtje screenshot-e tĂ« grafikĂ«ve nga Oracle Enterprise Manager, ku me kujdes theksonte me marker tĂ« kuq pikat kritike “pĂ«r qartĂ«si” (qĂ«, pĂ«r ta thĂ«nĂ« butĂ«, nuk ndihmonte shumĂ«). Dhe pikĂ«risht sipas kĂ«saj “fotografie” duhej bĂ«rĂ« diagnostikimi dhe zgjidhja. NdĂ«rkohĂ«, askush nuk kishte akses te Enterprise Manager-i i çmuar (nĂ« tĂ« dy kuptimet e fjalĂ«s), sepse sistemi Ă«shtĂ« kompleks dhe i shtrenjtĂ«, e “mos vallĂ« zhvilluesit klikojnĂ« ndonjĂ« gjĂ« dhe prishin gjithçka”. Prandaj zhvilluesit, nĂ« mĂ«nyrĂ« “empirike”, gjenin vendin dhe shkakun e ngadalĂ«simeve dhe publikoni njĂ« patch. NĂ«se letra kĂ«rcĂ«nuese nga DBA nuk vinte sĂ«rish nĂ« tĂ« ardhmen e afĂ«rt, tĂ« gjithĂ« merrnin frymĂ« tĂ« lehtĂ«suar dhe ktheheshin te detyrat e tyre aktuale (deri te Letra e radhĂ«s).

Por procesi i monitorimit mund tĂ« duket mĂ« i thjeshtĂ« dhe mĂ« miqĂ«sor, dhe mĂ« e rĂ«ndĂ«sishmja — i arritshĂ«m dhe transparent pĂ«r tĂ« gjithĂ«. TĂ« paktĂ«n pjesa e tij bazĂ«, si shtesĂ« ndaj sistemeve kryesore tĂ« monitorimit (tĂ« cilat, pa dyshim, janĂ« tĂ« dobishme dhe nĂ« shumĂ« raste tĂ« pazĂ«vendĂ«sueshme). Çdo DBMS Ă«shtĂ« e gatshme tĂ« ndajĂ« lirisht dhe plotĂ«sisht falas informacion pĂ«r gjendjen e saj aktuale dhe performancĂ«n. NĂ« tĂ« njĂ«jtĂ«n Oracle DB “tĂ« pĂ«rgjakshme”, pothuajse çdo informacion pĂ«r performancĂ«n mund tĂ« merret nga pamjet sistemore, duke filluar nga proceset dhe sesionet e deri te gjendja e buffer cache (pĂ«r shembull, DBA Scripts, seksioni “Monitoring”). Edhe nĂ« Postgresql ekziston njĂ« gamĂ« e tĂ«rĂ« pamjesh sistemore pĂ«r monitorimin e punĂ«s sĂ« DB, nĂ« veçanti ato kaq tĂ« pazĂ«vendĂ«sueshme nĂ« pĂ«rditshmĂ«rinĂ« e çdo DBA-je, si pg_stat_activity, pg_stat_database, pg_stat_bgwriter. NĂ« MySQL pĂ«r kĂ«tĂ« qĂ«llim ekziston madje njĂ« skemĂ« e veçantĂ« performance_schema. NdĂ«rsa nĂ« Mongo, profileri i integruar grumbullon tĂ« dhĂ«nat e performancĂ«s nĂ« koleksionin sistemor system.profile.

Kështu, duke përdorur një mjet për mbledhjen e metrikave (Telegraf, Metricbeat, Collectd) që mund të ekzekutojë kërkesa SQL të personalizuara, një ruajtje për këto metrika (InfluxDB, Elasticsearch, Timescaledb) dhe një mjet vizualizimi (Grafana, Kibana), mund të krijoni një sistem monitorimi mjaft të lehtë dhe fleksibël, i cili do të integrohet ngushtë me metrika të tjera të përgjithshme të sistemit (të marra, për shembull, nga serveri i aplikacionit, nga OS etj.). Për shembull, si është bërë te pgwatch2, ku përdoret kombinimi InfluxDB + Grafana dhe një grup kërkesash ndaj pamjeve të sistemit, të cilave gjithashtu mund t'u shtohen kërkesa të personalizuara.

Përveç kësaj

Dhe kjo është vetëm një listë e përafërt e asaj që mund të bëhet me bazën tonë të të dhënave përmes kodit të zakonshëm SQL. Jam i sigurt se mund të gjenden edhe shumë përdorime të tjera, shkruani në komente. Ndërsa për mënyrën se si (dhe më e rëndësishmja pse) e gjithë kjo mund të automatizohet dhe të përfshihet në pipeline-in tuaj CI/CD, do të flasim herën tjetër.

Burimi: habr.com

Blini hosting tĂ« besueshĂ«m pĂ«r faqe interneti me mbrojtje nga DDoS, serverĂ« VPS VDS đŸ”„ Blini hosting tĂ« besueshĂ«m pĂ«r faqe interneti me mbrojtje nga DDoS, serverĂ« VPS VDS | ProHoster