Wprowadzenie

Jednym z naj­czę­ściej sto­so­wa­nych wzor­ców pro­jek­to­wych w war­stwie dzie­dzi­no­wej jest wzo­rzec CQRS (Command Query Responsibility Segregation) oraz czę­sto wyko­rzy­sty­wa­ny razem z nim Event Sourcing. 

W 2012 roku pisa­łem o tym wzor­cu w kon­tek­ście opty­ma­li­za­cji wydajnosci:

Idea tego pomy­słu na tym, by nie opty­ma­li­zo­wać wydaj­no­ści sys­te­mu meto­dą, nie raz zgni­łe­go, kom­pro­mi­su, a podejść do pro­ble­mu dzie­ląc go na dwa pro­ble­my: zgod­ność mode­lu z rze­czy­wi­sto­ścią i wydaj­ność całe­go sys­te­mu. Pierwszy pro­blem roz­wią­zu­je­my two­rząc wier­ny model struk­tu­ry opi­su­ją­cej pro­duk­ty, dru­gi pro­blem ? wydaj­no­ści ? roz­wią­zu­je­my two­rząc dru­gi uprosz­czo­ny model pro­duk­tów, do celów szyb­kiej reali­za­cji kil­ku waż­nych ??zapy­tań? (np. publi­ka­cja on line uprosz­czo­nej posta­ci cen­ni­ka). ( CQRS czy­li kto, co i jak zama­wia i dostar­cza – Jarosław Żeliński IT-Consulting).

Rok póź­niej (2013), w toku prac nad kolej­nym pro­jek­tem, powsta­ła wątpliwość:

i to jest ten moment, w któ­rym ja chy­ba cze­goś nie zro­zu­mia­łem u Fowlera (i nie tyl­ko u nie­go) (CQRS czy­li jak osią­gnąć wydaj­ność c.d. – Jarosław Żeliński IT-Consulting)

Najczęściej CQRS opi­sy­wa­ny jest przez deve­lo­pe­rów, języ­kiem deve­lo­pe­ra, i moim zda­niem, z per­spek­ty­wy deve­lo­pe­ra oraz domyśl­ne­go uży­cia rela­cyj­ne­go mode­lu danych. M.Fowler poka­zu­je to tak:

(źr. M.Fowler)

Co ozna­cza, że sepa­ra­cja jest reali­zo­wa­na wyłącz­nie w posta­ci odręb­nych zesta­wów pole­ceń SQL, jed­nak do tej samej rela­cyj­nej bazy danych (na powyż­szym dia­gra­mie po lewej, czy­li tej samej ich struk­tu­ry) przy­kry­tej jej abs­trak­cją (środ­ko­wa część powyż­sze­go dia­gra­mu), uży­wa­na na uży­tek jed­no­li­te­go dostę­pu do niej. Warto pamię­tać, że ta abs­trak­cyj­na war­stwa to mapo­wa­nie obiek­to­wo-rela­cyj­ne (ORM, ang. Object Relational Mapping), rzecz w tym, że takie podej­ście owszem sepa­ru­je ele­men­ty imple­men­ta­cji logi­ki apli­ka­cji, ale nadal nie uwal­nia nas od zło­żo­no­ści zapy­tań SQL do rela­cyj­nej bazy danych. Przykładem może być ten (zresz­tą, uwa­żam bar­dzo dobry) opis: 

Dobrze, przejdź­my więc do gra­ficz­nej [rys. powy­żej] repre­zen­ta­cji wzor­ca, któ­ra powin­na pomóc nam zro­zu­mieć z czym tak na praw­dę mamy do czy­nie­nia… (CQRS i Event Sourcing – czy­li łatwa dro­ga do ska­lo­wal­no­ści naszych)

Należy tu zwró­cić uwa­gę na fakt, że CQRS to wzo­rzec archi­tek­to­nicz­ny czy­li dobra prak­ty­ka, a Event Sourcing to czę­sto mecha­nizm wbu­do­wa­ny w sys­tem zarzą­dza­nia rela­cyj­ną bazą danych. 

Separacja

Opiszę wer­sję PIM obu ww. wzor­ców, abs­tra­hu­jąc cał­ko­wi­cie od war­stwy tech­no­lo­gicz­nej, czy­li pomi­nę kwe­stię hen­dle­rów”, szyn bus” itp. To mam nadzie­ję oczy­ści opis z tego co nie jest isto­tą idei jaka, moim zda­niem, stoi a tymi wzor­ca­mi: opty­ma­li­za­cja zarzą­dza­nia prze­cho­wy­wa­ną informacją.

Niedawno, pod­su­mo­wu­jąc opis baz doku­men­to­wych i przy­kła­do­we­go pro­jek­tu, pisa­łem, że:

Dokumentowe bazy danych mają wie­le zalet (np. wydaj­ność) ale zale­tą, któ­ra czy­ni jest na praw­dę ??hitem? jest moż­li­wość cał­ko­wi­tej rezy­gna­cji z mode­lu rela­cyj­ne­go i języ­ka SQL, co w efek­cie daje ogrom­ne uprosz­cze­nie imple­men­ta­cji. ??Wyciągnięcie? z repo­zy­to­rium nawet bar­dzo skom­pli­ko­wa­ne­go struk­tu­ral­nie doku­men­tu, reali­zu­je­my tu jed­nym bar­dzo pro­stym pole­ce­niem. Identyczna ope­ra­cja w mode­lu rela­cyj­nym będzie wyma­ga­ła nie raz mon­stru­al­ne­go, trud­ne­go do testo­wa­nia, zapy­ta­nia SQL .Bazy doku­men­to­we ??nie boją się redun­dan­cji? (Projekt apli­ka­cji czy­li bazy doku­men­to­we – Jarosław Żeliński IT-Consulting).

Warto też nie zapo­mi­nać, że wzor­ce pro­jek­to­we są po to by roz­wią­zać pro­blem (speł­nie­nie wyma­gań) a nie po to by jest zasto­so­wać bo tak. 

Przyjmuje się, że CQRS to zasa­da, któ­ra mówi że każ­da meto­da w sys­te­mie powin­na być zakla­sy­fi­ko­wa­na do jed­nej z dwóch grup: 1. Command – są to meto­dy, któ­re zmie­nia­ją stan apli­ka­cji i nic nie zwra­ca­ją, 2. Query – są to meto­dy, któ­re coś zwra­ca­ją, ale nie zmie­nia­ją sta­nu apli­ka­cji. Autor ww. arty­ku­łu przywołuje: 

Dlaczego doko­nu­je­my podzia­łu jedy­nie metod na te, któ­re pobie­ra­ją dane oraz na te, któ­re zmie­nia­ją stan naszej apli­ka­cji? Możemy prze­cież zapro­jek­to­wać nasz sys­tem tak, aby tymi zada­nia­mi zaj­mo­wa­ły się osob­ne kla­sy. To jest głów­na róż­ni­ca mię­dzy dwo­ma podej­ścia­mi. ?Mówiąc o CQS myśli­my o meto­dach. Mówiąc o CQRS myśli­my o obiektach.?

I tu widzę pro­blem, bo bazy rela­cyj­ne cechu­ją się tym, że doku­men­ty (okre­ślo­ne struk­tu­ry danych mają­ce postać struk­tu­ral­ną lub nie) w nich nie ist­nie­ją, same tabe­le rela­cyj­ne zawie­ra­ją dane, ale nie nio­są żad­nej uży­tecz­nej infor­ma­cji. Niezależnie od tego czy chce­my zapi­sać doku­ment (zmie­nio­ny lub nowy) czy też do odczy­tać, musi­my wyko­nać, nie raz bar­dzo skom­pli­ko­wa­ny, kod zapy­ta­nia w języ­ku SQL. Separacja zapi­sów danych nowych (zmie­nio­ne też są nowe”) i ich odczy­ty­wa­nia, ma tu sens bo czy­ta­nie nie wyma­ga żad­nej kon­tro­li (nie sta­no­wi ryzy­ka dla sys­te­mu), zapis jest zawsze obło­żo­ny restryk­cja­mi bo stwa­rza ryzy­ko spo­wo­do­wa­nia błę­dów cało­ści struk­tu­ry danych. Po dru­gie czę­sto odczyt, jest w takiej bazie rozu­mia­ny jako raport czy­li treść two­rzo­na na pod­sta­wie wie­lu doku­men­tów, co dodat­ko­wo kom­pli­ku­je treść zapy­ta­nia SQL. 

O wzor­cu Event Sourcing nie będę się roz­pi­sy­wał, roz­wią­zu­je pro­blem zarzą­dza­nia histo­rią zmian danych w rela­cyj­nym mode­lu, przy jed­no­cze­snym usu­wa­niu redun­dan­cji danych, a jego opi­sy są łatwo dostęp­ne. Tu sku­piam sie jedy­nie na opi­sie wzor­ca na pozio­mie biz­ne­so­wym dzie­dzi­ny problemu.

Struktura komponentu Archiwum

Struktura Archiwum (opr. własne)

Powyższy dia­gram obra­zu­je archi­tek­tu­rę Archiwum Dokumentów (odpo­wia­da za zacho­wa­nie doku­men­tów i dostęp do ich tre­ści). Opis od prawej.

Dokument jako agre­gat, jest to ciąg zna­ków, któ­ry może mieć nada­ną mu struk­tu­rę . Podstawowa struk­tu­ra to nazwa oraz począ­tek i koniec doku­men­tu. Treść może mieć wewnętrz­ną struk­tu­rę :

(patrz Dokument…)

Podstawowym ele­men­tem Archiwum jest zbiór obiek­tów kasy Zachowany Dokument. Obiekty tej kla­sy są nośni­ka­mi doku­men­tów (cią­gów zna­ków jak wyżej), każ­dy ma okre­ślo­ną toż­sa­mość (iden­ty­fi­ka­tor doku­men­tu). Każda ope­ra­cja zapi­sa­nia lub przy­wo­ła­nia doku­men­tu to wywo­ła­nie wła­ści­we­go obiek­tu: przy­wo­łaj doku­ment (iden­ty­fi­ka­tor doku­men­tu). Zapis doku­men­tu wyglą­da ana­lo­gicz­nie, co ozna­cza, że ewen­tu­al­ny zapis pobra­ne­go wcze­śniej doku­men­tu o zmie­nio­nej tre­ści nad­pi­sze jest sta­rą wer­sję”. Jest wie­le sytu­acji gdy nie sta­no­wi to pro­ble­mu. Te przy­pad­ki, gdy chce­my mieć doku­ment jako draft” (edy­to­wal­ny) i wer­sję final­ną (nie­edy­to­wal­ny) reali­zu­je­my doda­ją obiek­to­wy atry­but sta­tus doku­men­tu” (draft|final). Jednak jest wie­le sytu­acji gdy histo­ria zmian jest potrzebna. 

Pierwsza rzecz: za histo­rię powi­nien odpo­wia­dać inny obiekt więc two­rzy­my dodat­ko­wą Klasę obiek­tów Zmiany. Separowanie histo­rii obiek­tów od nich samych ma dwie korzy­ści: nie prze­cią­ża­my Obiektów kla­sy Zachowany Dokument oraz może­my tę histo­rię (mecha­nizm) dodać póź­niej, gdy oka­że się że fak­tycz­nie jest potrzeb­ny. Po trze­cie, co jest tu bar­dzo waż­ne: w kla­sie Zachowany Dokument toż­sa­mość ma doku­ment, zaś w kla­sie Zmiany toż­sa­mość ma wer­sja doku­men­tu. Biorąc pod uwa­gę, że bar­dzo czę­sto żąda­my dostę­pu do doku­men­tów i bar­dzo rzad­ko do histo­rii ich zmian, pozwa­la to utrzy­mać mini­mal­ną wyma­ga­na licz­bę obiek­tów kla­sy Zachowany Dokument (a mogą to milio­ny). Praktyka poka­zu­je, że kom­fort pra­cy jest oce­nia­ny na pod­sta­wie czyn­no­ści wyko­ny­wa­nych czę­sto na co dzień, rzad­ko wyko­ny­wa­ne czyn­no­ści (odtwa­rza­nie histo­rii) to te, na któ­re czę­sto mamy znacz­nie wię­cej czasu.

Tu waż­na infor­ma­cja: doku­ment może być opi­sem fak­tu lub obiek­tu (czy­taj wię­cej o Separacja kon­tek­stu…). Ma to ogrom­ne zna­cze­nie, bo Archiwum (kasa Zachowany doku­ment) wca­le nie musi prze­cho­wy­wać doku­men­tów o iden­tycz­nej struk­tu­rze. Zaletą tego wzor­ca jest to, że nie muszą czy­li np. może­my w jed­nym jed­nym Archiwum trzy­mać doku­men­ty mimo tego, że ich struk­tu­ra ewo­lu­uje z cza­sem, co nie wyma­ga żad­nej refak­to­ry­za­cji bazy i jej migra­cji. Klasyfikacji tre­ści doku­men­tów może­my doko­nać z pomo­cą spe­cjal­nej struk­tu­ry iden­ty­fi­ka­to­ra doku­men­tu (sys­tem ten sta­le funk­cjo­nu­je w archi­wi­sty­ce: każ­dy doku­ment ma uni­kal­ną sygna­tu­rę, zawie­ra­ją­cą tak­że kla­sy­fi­ka­cję doku­men­tu). Można też dodać do każ­de­go doku­men­tu opi­su­ją­ce go meta­da­ne i użyć ich jako dodat­ko­wych atry­bu­tów kla­sy Zachowany doku­ment, ale lep­sze roz­wią­za­nie w dal­szej części. 

Trzeci ele­ment to kla­sa Hurtownia Danych. Najczęściej jest to inter­fejs do dedy­ko­wa­ne­go sys­te­mu rapor­to­wa­nia np. hur­tow­ni danych, sys­te­mu BI itp.. 

Przed resz­tą świa­ta” cała struk­tu­ra jest ukry­ta” (her­me­ty­za­cja) za obiek­tem kla­sy Menedżer Repozytorium. Tu jest cała logi­ka kon­tro­li dostę­pu do Archiwum oraz logi­ka adre­so­wa­na zapy­tań: pyta­nie o doku­ment będzie kie­ro­wa­ne do obiek­tów kla­sy Zachowany Dokument, pyta­nie o histo­rie okre­ślo­ne­go doku­men­tu do obiek­tów kla­sy Zmiany, wszel­kie żąda­nia danych zbior­czych i prze­li­cza­nych, do Hurtowni Danych. kom­po­nen­ty korzy­sta­ją­ce z tych usług nie zna­ją archi­tek­tu­ry tego zaple­cza”, dzię­ki cze­mu jakie­kol­wiek zmia­ny w tym zaple­czy” w żad­nym stop­niu nie będą doty­czy­ły resz­ty systemu. 

Po lewej stro­nie mamy (tu dwa) cie­ka­we obiek­ty. Są to dzie­dzi­no­we spi­sy doku­men­tów. Wyżej pisa­łem, że moż­na też dodać do każ­de­go doku­men­tu opi­su­ją­ce go meta­da­ne i użyć ich jako dodat­ko­wych atry­bu­tów kla­sy Zachowany Dokument, jed­nak to spo­wo­du­je, że przy­szła potrze­ba wpro­wa­dze­nia nowej kla­sy­fi­ka­cji spo­wo­du­je koniecz­ność refak­to­ry­za­cji obiek­tów kla­sy Zachowany Dokument (doda­nie kolej­ne­go atry­bu­tu). Aby tego unik­nąć, obiek­ty kla­sy Zachowany Dokument powin­ny odpo­wia­dać wyłącz­nie za zacho­wa­nie doku­men­tu (każ­dy ma toż­sa­mość w atry­bu­cie iden­ty­fi­ka­tor doku­men­tu). Jeżeli potrzeb­ne jest nam ope­ro­wa­nie doku­men­ta­mi na pod­sta­wie więk­szej ilo­ści jego cech (daty, war­to­ści pie­nięż­ne, oso­by pod­pi­su­ją­ce itp., tema­ty­ka tre­ści, inne) udo­stęp­nia­my akto­rom nie Menedżera Repozytorium (Aktor A) a Dziedzinowe Rejestry (Aktor B). Uzyskujemy dwie klu­czo­we korzy­ści: kon­tro­le dostę­pu do tre­ści (aktor może mieć dostęp tyl­ko do obiek­tów kla­sy Dziedzinowy Rejestr X) oraz moż­li­wość doda­wa­nia kolej­nych dzie­dzi­no­wych reje­strów bez koniecz­no­ści refak­to­ry­za­cji wła­ści­we­go Archiwum. Tak funk­cjo­nu­ją reper­to­ria w urzę­dach i sądach. 

Podsumowanie

Opisana archi­tek­tu­ra pozwa­la na sepa­ra­cję kon­tek­stów (aktu­ali­za­cja tre­ści i jej pobie­ra­nie). Pozwala na nie­za­leż­ne zarzą­dza­nie histo­rią doku­men­tów. Spełnia zasa­dę OOP (ang. Open Close Principia) czy­li archi­tek­tu­ra jest zamknię­ta na zmia­ny i otwar­ta na roz­sze­rze­nia. Oparta jest na idei wzor­ców powszech­nie zna­nych jako CQRS i Event Sourcing. 

Praca z poje­dyn­czym doku­men­tem (w szcze­gól­no­ści jego stwo­rze­nie i aktu­ali­za­cja) jest pro­sta wyma­ga tyl­ko (i aż) zna­jo­mo­ści jego iden­ty­fi­ka­to­ra. Tu mamy do czy­nie­nia ze zmia­ną tre­ści (sta­nu) w sys­te­mie (CQRS).

Szybkie wyszu­ka­nie doku­men­tu na bazie wyra­fi­no­wa­nych zapy­tań da nam hur­tow­nia danych (tu zastrze­żo­na dla Aktora A), któ­ra w tabli­cy fak­tów zawie­ra iden­ty­fi­ka­tor doku­men­tu, z któ­re­go pobra­no dane (tu osob­ny temat to hur­tow­nie danych i wie­lo­wy­mia­ro­we mode­le danych do obli­czeń i rapor­to­wa­nia). Jest to czy­ta­nie, ope­ra­cja nie zmie­nia­ją­ca sta­nu sys­te­mu (CQRS). Dotyczy to tak­że zmie­nio­nych, histo­rycz­nych wer­sji dokumentów. 

Obu tych ope­ra­cji moż­na doko­nać tak­że za pośred­nic­twem Dziedzinowego Rejestru (Aktor B).

Opisana tu kon­struk­cja zosta­ła wyko­rzy­sta­na w sys­te­mie Generator Ofert dla Biura Polonijnego Kancelarii Senatu oraz w ?System Wspierania Realizacji Zadań ŻW? dla Oddziału Zabezpieczenia Żandarmerii Wojskowej. W obu przy­pad­kach jed­nym z klu­czo­wych wyma­gań był czas wyko­na­nia imple­men­ta­cji, dzie­dzi­no­wa kon­tro­la dostę­pu do doku­men­tów, oraz moż­li­wość pra­cy z for­mu­la­rza­mi o czę­sto zmie­nia­nej struk­tu­rze i for­mu­la­rza­mi nowy­mi, bez koniecz­no­ści inge­ren­cji developera. 

Bibliografia

Šilingas, D., & Butleris, R. (2009). Towards imple­men­ting a fra­me­work for mode­ling softwa­re requ­ire­ments in MagicDraw UML. Information Technology and Control, 38(2).
Papamarkos, G., Zamboulis, L., & Poulovassilis, A. (2015). Xml Databases. School of Computer Science and Information Systems, Birkbeck College, University of London. http://www.dcs.bbk.ac.uk/~sven/adm08/xmlDBs.pdf
Pacheco, V. F. (2018). Microservice Patterns and Best Practices: Explore pat­terns like CQRS and event sour­cing to cre­ate sca­la­ble, main­ta­ina­ble, and testa­ble micro­se­rvi­ces. Packt Publishing Ltd.

Jarosław Żeliński

Jarosław Żeliński: autor, badacz i praktyk analizy systemowej organizacji: Od roku 1991 roku, nieprzerwanie, realizuje projekty z zakresu analiz i projektowania systemów, dla urzędów, firm i organizacji. Od 1998 roku prowadzi samodzielne studia i prace badawcze z obszaru analizy systemowej i modelowania (modele jako przedmiot badań: ORCID). Od 2005 roku, jako nieetatowy wykładowca akademicki, prowadzi wykłady i laboratoria (ontologie i modelowanie systemów informacyjnych, aktualnie w Wyższej Szkole Informatyki Stosowanej i Zarządzania pod auspicjami Polskiej Akademii Nauk w Warszawie.) Oświadczenia: moje badania i publikacje nie mają finansowania z zewnątrz, jako ich autor deklaruję brak konfliktu interesów. Prawa autorskie: Zgodnie z art. 25 ust. 1 pkt. 1) lit. b) ustawy o prawie autorskim i prawach pokrewnych zastrzegam, że dalsze rozpowszechnianie artykułów publikowanych w niniejszym serwisie jest zabronione bez indywidualnej zgody autora (patrz Polityki Strony).

Ten post ma 2 komentarzy

  1. Tomek

    Cześć Jarek, jak zawsze w swo­ich arty­ku­łach spraw­nie prze­ka­zu­jesz por­cję wie­dzy w skon­den­so­wa­nej for­mie, dzięki 🙂
    Jedno pyta­nie o zbio­ry danych. W arty­ku­le mamy prze­cho­wy­wa­nie zarów­no ostat­niej wer­sji doku­men­tu jak i histo­rii zmian – co jest dla klien­ta źró­dłem praw­dy? Co w przy­pad­ku, kie­dy doj­dzie do nie­spój­no­ści pomię­dzy zapi­sa­mi histo­rii i ostat­nią wer­sją doku­men­tu. Event Sourcing zakła­da prze­cho­wy­wa­nie jedy­nie histo­rii zmian (zda­rzeń), jako jedy­ne­go źró­dła praw­dy o sta­nie obiektu.

    Z naj­lep­szy­mi nowo­rocz­ny­mi życzeniami! 🙂

    1. Jarosław Żeliński

      To dobre pyta­nie, bo ten wzo­rzec ope­ru­je na pozio­mie tre­ści doku­men­tu”. Podstawowe zało­że­nie to zaufa­nie do bazy doku­men­tów (nie kwe­stio­nu­je­my jako­ści danych), czy­li źró­dłem praw­dy jest posia­da­na baza danych. Wdrożenie tego wzor­ca ma nie­ja­ko dwie wersje:
      1. Istnieje jeden wła­ści­wy aktu­al­ny doku­ment (aktu­ali­za­cje nad­pi­su­ją sta­re dane) oraz chro­no­lo­gicz­na kolek­cja wpro­wa­dza­nych zmian (aktu­ali­za­cji tego dokumentu).
      2. Istnieją wszyst­kie histo­rycz­ne wer­sje doku­men­tu (nie nad­pi­su­je­my) i wła­ści­wy aktu­al­ny doku­ment na wierz­chu”, oraz chro­no­lo­gicz­na kolek­cja wpro­wa­dza­nych zmian (aktu­ali­za­cji tego dokumentu).

      W wer­sji pierw­szej istot­na jest tyl­ko aktu­al­na treść doku­men­tu i histo­ria fak­tów jego aktu­ali­za­cji (wyma­ga­nia biz­ne­so­we). Nie potrze­bu­je­my sta­rych wer­sji” ale chce­my wie­dzieć kie­dy kto i co zmie­niał. W wer­sji dru­giej chce­my mieć dostęp ad-hoc tak­że do sta­rych wer­sji” (inne wyma­ga­nie biz­ne­so­we). Nie wyobra­żam więc sobie nie­spój­no­ści pomię­dzy zapi­sa­mi histo­rii i ostat­nią wer­sją dokumentu”. 

      Pojęcie stan obiek­tu” jest tu zwod­ni­cze, bo tak na praw­dę cho­dzi o okre­ślo­ne jego atry­bu­ty, lub po pro­stu doku­ment” w przy­pad­ku baz doku­men­to­wych. Jeżeli poja­wia się tu pro­blem to tyko wte­dy, gdy doku­ment” to tak na praw­dę raport SQL” z bazy o rela­cyj­nym mode­lu danych, i tu fak­tycz­nie jest wal­ka o jakiej piszesz. Teoretycznie jest moż­li­we odtwo­rze­nie dowol­ne­go sta­nu z histo­rii odkrę­ca­jąc” wstecz zmia­ny ale to gehen­na (no i to o czym piszesz czy­li ryzy­ko nie­spój­no­ści). Dlatego moto­ry baz danych SQL mają takie mecha­ni­zmy wbu­do­wa­ne, ale to tyl­ko czę­ścio­we roz­wią­za­nie problemu.

      Generalnie na świe­cie nastę­pu­je powol­ne odej­ście od sto­so­wa­nia rela­cyj­ne­go mode­lu danych dla dokumentów/formularzy, mię­dzy inny­mi z tego powo­du. Bazy NoSQL roz­wią­zu­ją więk­szość pro­ble­mów jaki wska­za­łeś. Lada moment uka­że się w USA (IGI Global) mój arty­kuł na ten temat (https://​www​.igi​-glo​bal​.com/​b​o​o​k​/​m​a​n​a​g​e​m​e​n​t​-​s​t​r​a​t​e​g​i​e​s​-​d​i​g​i​t​a​l​-​e​n​t​e​r​p​r​i​s​e​-​t​r​a​n​s​f​o​r​m​a​t​i​o​n​/​2​4​4​649).

Dodaj komentarz

Witryna wykorzystuje Akismet, aby ograniczyć spam. Dowiedz się więcej jak przetwarzane są dane komentarzy.