Vendor Lock In - ce este, cum îl putem evita?

Nu, nu asta am vrut sa zic.
Exemplul cu bindEvent e unul simplu, poate va continua sa mearga daca inlocuiesti jquery cu orice alta librarie.

Dar daca pentru alte functii, daca nu va mai fi asa, daca functiile au un alt flow in libraria cu care vrei sa inlocuiesti jQuery?
Un alt exemplu (bun zic eu) ti-am dat deja cu promises si async \ await.
Si ti-am dat si exemplul in care presupunem ca la un moment dat, tot ce face jQuery se poate nativ. Ce vei face in acest caz, vei ajunge sa mentii acel layer?

1 Like

Deci… practic aș avea ceva de genul ăsta ?

var vendor_jQuery = {
  favoriteElement : function(){
    $('.toggle-favorite').on('click', function(e){
      $(e.currentTarget).addClass('is-favorite');
    });
  }
};

@kilogrammer: nu știu cum înveți tu (sau alții) dar eu învăț mai ușor cu cel mai simplu exemplu. Întâi încerc să pricep care este treaba asta cu separarea vendor-ului, apoi îmi voi face probleme cu restul. :smile:

Exemplul tău cu promises nu știu dacă e tocmai potrivit, dat fiind faptul că, pentru a implementa asta ar trebui să modifici codul tău. Ori a evita vendor lock-in presupune a schimba vendorul fără a umbla și în codul tău.

1 Like

In sfarsit ridici o problema intr-un mod constructiv.

Afisarea (apelul la showAwesomeProducts) se face in acelasi moment in timp, indiferent de modul de livrare al codului.

E posibil sa fie necesare schimbari in modul in care instantiezi layer-ul. Daca introduci un bus asincron de exemplu, presupun ca (intr-o arhitectura curata), vei il vei si instructiona pe acel bus sa emita evenimente in puncte de inflexiune importante.

Acel lucru il faci oricum, daca ai o arhitectura curata. Poate nu asa, poate numesti lucrurile altfel, dar in principiu vei avea o componenta ceva in plus pentru acea metoda de livrare numita “asincron”.

Ei si layer-ului ii poti injecta acel event bus prin constructor, iar apoi el poate manageria corect situatia.

Summa summarum (valabil chiar daca am inteles gresit ce ai scris): la introducerea unei alte strategii introduci un nou concept, concept care poate adapta celelalte componente. Afisarea lui “awesome products” va fi mereu aceeasi din perspectiva end user-ului, atata timp cat business-ul ramane la fel.

1 Like

Pai daca vendor-ul, ala nou, foloseste feature-uri gen promises sau async / await, ce faci?
Promises sunt putin mai bine decat callbacks si sunt deja folosite de ceva vreme.

Abordarea asta sa creem un layer duplicat, dar nu stiu exact motivele de ce o facem, cu ce pros and cons, nu mi se pare o optiune buna.
Nici macar nu e realista. jQuery e doar un singur vendor. Ce faci pentru restul, creezi layere pentru fiecare in parte?
Eu unul nu am vazut nicaieri asa ceva.

Nu e mai intelept, practic si realist sa privesti din alte perspective problema?
Eu nu zic ca vendor lock-in nu e problema reala, dar nu poti discuta atat de simplu, ca faci un layer unde duplici, si asta e o solutia “banala” si miraculoasa care trebuie implementata

1 Like

Da. Lucrul asta oricum vrei sa il faci, duce oricum la un cod mai expresiv. Poti privi evitarea de vendor lock-in chiar ca efect secundar, nu ca motivatie principala. In fapt, e chiar mai bine sa te gandesti din acest punct de vedere.

DESI REPET inca o data: acesta nu este sfarsitul evitarii vendor lock-in-ului, e doar un inceput simplu pe care il poate face oricine. (repet pentru ca cu siguranta o sa apara iar un alt Gica Contra care se limiteaza la ce spun; ce vorbim aici e doar un exemplu concret, lucrurile concrete pe care le faci intr-un proiect complex sunt diverse si pot deveni complexe)

1 Like

Daca ai nevoie de un alt vendor concomitent cu jQuery care indeplineste aceeasi sarcina (abstractizeaza / faciliteaza lucrul cu DOM-ul), atunci ai o problema grava.

Alti vendori vor fi responsabili cu alte aspecte, nu cu fix acelasi lucru. In orice moment din timp nu vei avea mai mult de un vendor care se ocupa de acelasi lucru.

@kilogrammer: dacă ai și un răspuns la întrebarea ta, ar fi util :smiley:

1 Like

@iamntz

Vendor lock-in e ceva mai complex care tine de mai multi factori.
Nu inseamna doar sa creezi layere doar de frica ca o librarie simpla de UI extrem de populara nu va mai fi.

Ar trebui toti sa stim cat e de apetisant si atragator sa creem tot felul de abstractizari pe baza le ce citim, mi se pare important sa incerci sa le intelegi, caci unele abordari pun amprenta in mod serios la marimea codului si complexitate.

Poate nici nu e necesar sa ai vreun layer. Daca iti faci propria librarie/framework? :smiley: Ca tot se vorbea si asta.
Aia tot vendor lock in se cheama? Sau o fi self-lock in Lol

Ai surprins bine ideea.

Iar in front-end (concret, pentru tine, caci tu ai deschis topicul) nu imi imaginez scenarii cu mult mai complexe fata de ce ai ilustrat deja.

Acel layer anticorruption se poate dovedi util si in alte situatii, dintre cele mai “ciudate”. De exemplu, ai o aplicatie multi-tenant pe care o folosesti tu insuti ca si companie. In interiorul companiei tu ai control complet asupra browserelor folosite, deci daca apare o incompatibilitate intre jQuery si un anumit browser, dai directiva de sus tuturor angajatilor sa instaleze browserul X, versiunea Y, si ai terminat balciul.

DAR pe parcurs apare o alta firma care vrea sa iti inchirieze serviciile. Se dovedeste ca acea companie are ea insasi nevoile ei in termeni de browsere si versiuni. Compania aia vrea sa iti plateasca o caruta de bani pentru faptul ca-i inchiriezi aplicatia.

Ce faci? Simplu: ai anticorruption layer-ul, totul este practic existent. Duplici layer-ul tau si il peticesti asa incat sa functioneze pe browserele lor vechi. Timp de executie: o zecime din cat ti-ar lua sa faci alte artificii. Primesti: caruta cu bani cu bratele deschise.

E un exemplu relativ des intalnit de situatie in care abilitatea de a te adapta la piata conteaza enorm pentru o afacere. Aici anticorruption layer bate realitatea: adaptabilitate rapida cu ROI mare si riscuri minime.

3 Likes

Divide et impera, ffs, ar trebui sa visam asta.

Eu am inteles ca trebuie sa faci un wrapper pentru fiecare unitate (fie ea functie, metoda sau orice altceva) care foloseste direct ceva ce exista in jQuerry dar nu si in afara.

Idem.

[quote=“flavius, post:19, topic:2376”]
Sau cum ar zice Uncle Bob: vendorul e impachetat intr-un plug-in.
[/quote]Iar acel plug-in va fi singura parte de modificat, cand treci de la un vendor la altul. Cu mentiunea ca rescrierea acelui plugin trebuie testata serios. (Si aici ideal ar fi TDD, for safety…)

OK, eu am incercat sa scriu niste cost, dar nu-mi dau seama (decat vag, a.k.a. via intuition_that_can’t_be_trusted) cu ce este mai nasol

[quote=“kilogrammer, post:26, topic:2376”]
Dar daca pentru alte functii, daca nu va mai fi asa, daca functiile au un alt flow in libraria cu care vrei sa inlocuiesti jQuery?
[/quote]Sa zicem ca tu vrei sa afisezi toate produsele pe o pagina.
Poti face asta cu vendor lock-in
vendor_locked.php

<?php
require_once('framework1.php');    require_once('framework2.php');
function show_product_details($i){
        //product details
    echo $framework1->get_details('item'.$i, 'name').
         $framework1->get_details('item'.$i, 'price').
         $framework1->get_details('item'.$i, 'image').
         $framework1->get_details('item'.$i, 'detail1').
         $framework1->get_details('item'.$i, 'detail2');
        //price history for the last 30 days
    echo $framework2->get_price_history('item'.$i, 30); // 30 days
}
function show_all_products(){
    $nr_of_products = $framework->get_item_count();
    for($i=1;$i=$nr_of_producs;$i++){
        show_product_details($i);
}

decat
vendor_unlocked.php

<?php
require_once('framework1.php');    require_once('framework2.php');
function get_products_count(){
    return $framework1->get_item_count();
} /* Anti_corruption */
function echo_product_detail($db_field_nr, $item_detail){
    $item_nr='item'.$db_field_nr;
    echo $framework1->get_details($item_nr, $item_detail);
} /* Anti_corruption */
function echo_product_price_history($db_field_nr,$period_in_days){
    $item_nr='item'.$db_field_nr;
    echo $framework2->get_price_history('item'.$item_nr, $period_in_days);
} /* Anti_corruption */
function echo_product_details($product){
    echo_product_detail($product,'name');
    echo_product_detail($product,'price');
    echo_product_detail($product,'image');
    echo_product_detail($product,'detail1');
    echo_product_detail($product,'detail2');
    echo_product_price_history($product, $period_in_days);
} /* Safe Code */
function show_all_products(){
   $products_count=get_products_count();
    for($i=1;$products_count;$i++)
        echo_product_details($i, 30); // 30 days
} /* Safe Code */

Si ma refer scrict d.p.d.v. al anti-corruption-layer, nu al divide-et-impera…
Adica mi-e mai usor sa citesc codul de jos, si am impresia ca cel de sus kinda smells, dar nu-mi pot da seama, practic, why or how… desi instinctul imi spune ca acesta ar fi un exemplu cu ce este de dorit… A little help, please?

Vreau sa spun, ati dat exemple pe o functie sau doua, dar nu si cum functioneaza acele functii, intr-un A/B sheet of code…

Cred ca am inteles…
Daca (pe langa codul scris anterior) am nevoie de o functie care sa arate mai putine detalii, una care sa arate doar detaliile, etc., pot face asa
vendor_locked2.php

<?php 
require_once("vendor_locked.php");
function show_product_details_small($i){
        //product details
    echo $framework1->get_details('item'.$i, 'name').
         $framework1->get_details('item'.$i, 'price').
         $framework1->get_details('item'.$i, 'image');
}
function show_product_details_only($i){
         $framework1->get_details('item'.$i, 'detail1').
         $framework1->get_details('item'.$i, 'detail2');
        //price history for the last 30 days
    echo $framework2->get_price_history('item'.$i, 30); // 30 days
}

sau asa
vendor_unlocked2.php

<?php
require_once("vencor_unlocked.php");

function echo_product_details_small($product){
    echo_product_detail($product,'name');
    echo_product_detail($product,'price');
    echo_product_detail($product,'image');
} /* Safe Code */
function echo_product_details_only($product){
    echo_product_detail($product,'detail1');
    echo_product_detail($product,'detail2');
    echo_product_price_history($product, 30); // 30 days
} /* Safe Code */

si in felul asta chiar am mai putin de modificat in vendor_unlocked decat in vendor_locked, daca schimb vendor-ul… plus ca este mai lizibil, pentru un non-programator.

Am inteles corect?

Costa. Chiar si cand e mica.
a) timp - sa faci acele functii wrapper
b) efort cognitiv din partea celorlalti - sa inteleaga functii noi, decat functii jquery (pe care multi le cunosc).
c) noile functii/layer-e pot ele insele sa creeze noi bug-uri

Retine: nu spun ca nu-i buna protectia, dimpotriva. Dar costa. Orice abstractie suplimentara costa. Fuck it - orice cod suplimentar costa. Cheia aici este cat de multe avantaje ofera acel cost, altfel nu se justifica. Dat fiind ca uneori avantajele nu justifica acel cost, rezulta de aici ca, uneori, acel layer de protectie nu este o solutie economica buna.

Pentru un programator, orice layer aditional de protectie este bun. De acord. Pentru client, nu. Ghici cine mana caii.

Cat despre ce-i vendor lockin, nu cunosc teoria. Dar stiu ca, daca folosesti interfete, cel putin in php, esti pe drumul bun. Practic, orice nu reflecta detalii de implementare poate fi substituit in stil liskovian, iar tocmai acea posibila inlocuire (cu o alta implementare) ofera flexibilitate.

N-as spune ca o simpla functie wrapper e destul pt a te feri de lockin, dar e un inceput. Mai importante sunt contractele dintre aplicatia ta si vendori (cred ca asta-i anticorruption layer-ul de care vorbeste flavius).

2 Likes

Ai zis-o mai clar decat mine.

Am gasit un exemplu din .NET cautand prin bookmark-uri, e despre repository pattern, un pattern foarte des intalnit, poate e util unora:

https://lostechies.com/jimmybogard/2012/09/20/limiting-your-abstractions/

But is an ORM something that requires abstraction? I don’t think so – abstracting something like an ORM actively prevents me from using powerful features. An ORM is already an abstraction, we really need to question whether we need to abstract an abstraction.

Daca va intrebati, tipu’ asta a creat niste framework-uri populare.

https://lostechies.com/jimmybogard/2012/10/08/favor-query-objects-over-repositories/

https://lostechies.com/jimmybogard/2009/09/11/wither-the-repository/

http://www.thereformedprogrammer.net/is-the-repository-pattern-useful-with-entity-framework/

My point is, vendor lockin nu e musai o problema pentru toata lumea, pentru orice framework / library, pentru orice proiect, in orice situatie.

Dar nu are rost ne convingem unii pe altii, nici unul din noi nu are adevarul absolut. Cred ca cea mai mare capcana e sa ai impresia ca intotdeauna stii calea corecta.

2 Likes

@Sapioit - as folosi interfete:
Aici, contractul ProductDetails sta intre implementare si alte componenete ale aplicatiei. Pt a functiona, orice modul/componente/serviciu extern trebuie sa foloseasca de-acum acea interfata:
<?php

interface ProductDetails
{
    public function echo_product_details_small(Product $product);

    public function echo_product_details_only(Product $product);
}

class Impl1 implements ProductDetailts
{
    public function echo_product_details_small(Product $product)
    {
        echo_product_detail($product,'name');
        echo_product_detail($product,'price');
        echo_product_detail($product,'image');
    }

    public function echo_product_details_only(Product $product)
    {
        echo_product_detail($product,'detail1');
        echo_product_detail($product,'detail2');
        echo_product_price_history($product, 30);
    }    
}

class Impl2 implements ProductDetails
{
    public function __construct($framework1, $framework2)
    {
        $this->framework1 = $framework1;
        $this->framework2 = $framework2;    
    }

    function show_product_details_small(Product $product)
    {
        $i = $product->getId();
        echo $this->framework1->get_details('item'.$i, 'name')
                 . $this->framework1->get_details('item'.$i, 'price')
                 . $this->framework1->get_details('item'.$i, 'image');
    }

    function show_product_details_only(Product $product)
    {
        $i = $product->getId();
         echo $this->framework1->get_details('item'.$i, 'detail1')
                  . $this->framework1->get_details('item'.$i, 'detail2');
        echo $this->framework2->get_price_history('item'.$i, 30);
    }    
}

Impl1 si Impl2 se pot exclude sau pot conlucra - una pt productie, alta pentru testing, whatever. Ideea este ca implementarile se pot schima. Dar interfata este aceeasi. Un astfel de grup de interfete pot fi, conceptual, un layer de protectie/“indirection”.

1 Like

Pai, sa zicem ca daca si eu am inteles corect, ideea e sa ai un filtru prin care sa apelezi functii / metode din framework-urile la care nu vrei sa ai “vendor lock-in”.
Cred ca ar trebui sa ai si fisiere separate, astfel incat sa poti sa-l stergi pe primul si sa-l rescrii de la 0 daca este nevoie.
Uite cam cum ar fi folosind clase (parerea mea):
Presupunem fisierele:
framework1.php si framework2.php care ne pune la dispozitie niste clase Foo si Bar cu implementari diferite, dar care ofera informatii despre produs(e) sa zicem una apeleaza API-ul Foo, alta API-ul Bar.
Plecam de la o arhitectura MVC - GET /products → ProductController.php

public function listAction()
{
    $products = $this->getModel('products')->getAll();
    return $this->getView('product/list', $products);
}

in views/product/list.php am avea ceva de genul:

<?php
foreach($products as $product)
{ ?>
    <div>
        <a href="<?php echo $product->getUrl() ?>><?php echo $product->getName() ?></a>
    </div>
<?php 
} ?>

In view avem 2 metode getUrl si getName astfel metodele le putem pune fie in model fie in clasele pe care le extine.

class ProductModel extends Product
{
    public function __call($name, $args)
    {
        if(substr($name, 0, 3) === 'get'){
            return $this->getData(substr($name, 3, strlen($name)));
        }
    }
}

Acum clasa Product implementeaza o interfata care stie de ce metode avem nevoie in cazul acesta . Aceasta clasa va fi layerul pentru anti vendor lock-in.
Exemplu pt vendorul Foo:

class Product implements ProductInterface
{

    public function getAll()
    {
        return array('1' => new Foo());
    }

    public function getData($field)
    {
        return 'Foo' .'_' . $field;
    }
}

Exemplu pt vendorul Bar:

class Product implements ProductInterface
{

    public function getAll()
    {
        return array('2' => new Bar());
    }

    public function getData($field)
    {
        return 'Bar' .'_' . $field;
    }
}

Si exemplu pt interfata:

interface ProductInterface
{
    public function getAll(); // : array; PHP-7

    public function getData($field); // : string;

}

Clasa Product implementeaza metodele din interfata, dar logica din ele poate fi complet diferita. Conteaza sa primeasca aceasi parametrii si sa returneze la fel (PHP 7). In interfata poti adauga cate metode vrei, in functie de ce este util in logica aplicatiei tale, dar ar trebui sa te opresti la informatiile aduse de framework-urile folosite. Poti implementa mai multe interfete, de exemplu TaxInterface care poate folosi un provider de geolocation, dar as zice daca nu sunt oferite de acelasi vendor, sa implementezi in layere (clase) diferite, aici de exemplu Product sa tina o referinta catre Price care stie cum sa calculeze pretul cu taxa (si o sa ajungi sa vezi Dependency Injection).

1 Like

@kilogrammer - tipul e sceptic la abstractizarea unei abstractizari. Dar cred ca se refera la cazul cand faci o metoda precum getProducts() peste $orm->fetchRows('table_products'). @flavius cred ca ar fi de acord. Si eu, dar nu fiindca avem o metoda care nu reflecta detalii de implementare neaparat, dar, mai important zic eu, este ca getProducts() are insemnatate semantica pt client (the ubiqitou… wtf… of, ce cuvant greu… limbajul universal). Pe asta pluseaza flavius, nu numai pt layer de dragul lui, dar si ca metoda de traducere directa a business-ului (asta fiind DDD). Flavius vorbeste despre 2 lucruri: vendor lockin si UL. Nu au legatura una cu cealalta, dar sunt complementare.

2 Likes

Voiam sa iti raspund la un alt raspuns precedent de-al tau in care voiam sa iti spun ceea ce tocmai ai scris in paragraful citat. Nu mai e necesar acum, ai inteles corect ideile mele.

1 Like

da, si in acest topic s-a discutat despre layer peste jQuery

aplici domain driven design in orice? totul e un vendor lock in?
iti creezi scenarii ipotetice ca ai putea sa faci una si cealalta in nu stiu ce situatii de aplicatii, fara a avea un exemplu concret?

1 Like

In foarte multe discutii, asta lipseste. E usor sa spui ca teoretic e asa, iar aici asa, dar practic, cand ai requirements si code in fata, se scminba treaba, de multe ori. Asta fiind ca cei fara experienta vad teoria pe alt nivel de abstractii decat codul, desi cele doua nivele de abstractie sunt intertwined (impletite? Incolacite?) destul de adanc…