{"id":58,"date":"2026-11-05T21:41:51","date_gmt":"2026-11-05T19:41:51","guid":{"rendered":"https:\/\/zorin.ro\/index.php\/2026\/11\/05\/cum-am-migrat-un-e-commerce-cu-mii-de-produse-de-la-python-2-la-python-3-13-fara-sa-rescriem-totul-de-la-zero\/"},"modified":"2026-11-05T22:10:08","modified_gmt":"2026-11-05T20:10:08","slug":"cum-am-migrat-un-e-commerce-cu-mii-de-produse-de-la-python-2-la-python-3-13-fara-sa-rescriem-totul-de-la-zero","status":"publish","type":"post","link":"https:\/\/zorin.ro\/index.php\/2026\/11\/05\/cum-am-migrat-un-e-commerce-cu-mii-de-produse-de-la-python-2-la-python-3-13-fara-sa-rescriem-totul-de-la-zero\/","title":{"rendered":"Cum am migrat un e-commerce cu mii de produse de la Python 2 la Python 3.13 f\u0103r\u0103 s\u0103 rescriem totul de la zero"},"content":{"rendered":"<p>C\u00e2nd preiei un magazin online scris prin 2012\u20132014, impulsul clasic este s\u0103 \u0219tergi tot \u0219i s\u0103-l refaci de la zero \u00een cel mai recent framework la mod\u0103. Pui FastAPI, microservicii \u0219i un frontend greoi \u00een JavaScript care descarc\u0103 sute de megabi\u021bi de module.<\/p>\n<p>Doar c\u0103 \u00een via\u021ba real\u0103 afacerile nu func\u021bioneaz\u0103 a\u0219a. Dac\u0103 magazinul vinde bine, paginile se deschid \u00een sub 50 de milisecunde, indexarea \u00een Google e impecabil\u0103 \u0219i clien\u021bii s-au obi\u0219nuit cu interfa\u021ba, o rescriere complet\u0103 e adesea o capcan\u0103. Pierzi pozi\u021bii SEO acumulate \u00een zece ani, introduci zeci de bug-uri \u00een checkout \u0219i ri\u0219ti s\u0103 blochezi afacerea luni \u00eentregi.<\/p>\n<p>Exact aceasta a fost situa\u021bia noastr\u0103: un magazin cu peste 11.000 de anvelope active, sincroniz\u0103ri zilnice automate \u0219i o vitez\u0103 de reac\u021bie excelent\u0103, construit pe Python 2.7, Tornado 4.2 \u0219i SQLAlchemy 1.1.<\/p>\n<p>Problema? Pe un server nou cu Debian 13, Python 2 nu mai exist\u0103 \u00een repo-urile oficiale, iar serverele externe cer protocoale de securitate (TLS 1.3) pe care vechiul stack nu le mai poate sus\u021bine.<\/p>\n<p>Am ales modernizarea chirurgical\u0103 a codului. Am p\u0103strat structura bazei de date \u0219i template-urile HTML verificate \u00een timp, dar am adus motorul interior pe Python 3.13, Tornado 6.5 \u0219i SQLAlchemy 2.0.<\/p>\n<figure style=\"margin: 24px 0; text-align: center;\"><figcaption style=\"margin-top: 8px; font-size: 0.875rem; color: #778899; font-style: italic;\"><\/figcaption><\/figure>\n<h2>Cum ar\u0103ta stack-ul tehnic din anii 2014\u20132018<\/h2>\n<p>Ca s\u0103 \u00een\u021belegem de unde am plecat, merit\u0103 s\u0103 ne uit\u0103m pu\u021bin \u00een fi\u0219ierul de dependen\u021be g\u0103sit \u00een backup-ul proiectului. Totul respira aerul genera\u021biei trecute de dezvoltare web:<\/p>\n<pre><code class=\"language-text\">BeautifulSoup==3.2.1\r\nlxml==2.3.6\r\nmechanize==0.2.5\r\nMySQL-python==1.2.5\r\nSQLAlchemy==1.1.15\r\ntornado==4.2\r\nWhoosh==2.7.0\r\nWTForms==2.0.2\r\nwtforms-tornado==0.0.2\r\nxlrd==0.9.3<\/code><\/pre>\n<p>Pe scurt, aveam:<\/p>\n<ul>\n<li>Un Tornado 4.2 bazat pe un event loop intern (IOLoop) scris \u00eenainte ca <code>asyncio<\/code> s\u0103 fie integrat nativ \u00een biblioteca standard Python.<\/li>\n<li>Un driver de baz\u0103 de date (<code>MySQL-python<\/code>) care compila direct cu leg\u0103turi C vechi, incompatibile cu bibliotecile moderne de sistem de pe un Linux proasp\u0103t.<\/li>\n<li>O versiune de SQLAlchemy (1.1) care \u00eenc\u0103 func\u021biona pe stilul vechi de interog\u0103ri cu paranteze drepte \u00een clauzele select.<\/li>\n<li>O punte de leg\u0103tur\u0103 \u00eentre WTForms \u0219i Tornado (<code>wtforms-tornado<\/code>), abandonat\u0103 complet pe GitHub de prin 2016.<\/li>\n<li>O bibliotec\u0103 clasic\u0103 pentru citirea fi\u0219ierelor Excel (<code>xlrd<\/code>), r\u0103mas\u0103 blocat\u0103 \u00een timp.<\/li>\n<\/ul>\n<p>Ce a r\u0103mas neschimbat \u0219i valoros? Arhitectura de c\u0103utare. \u00cen loc s\u0103 facem interog\u0103ri greoaie de tip LIKE \u00een tabele mari la fiecare c\u0103utare pe site, proiectul folosea biblioteca de c\u0103utare full-text Whoosh. Ea cite\u0219te un view compact din baza de date \u0219i men\u021bine un index binar local pe disc, oferind filtrare fa\u021betat\u0103 (l\u0103\u021bime, \u00een\u0103l\u021bime, diametru, sezon, clas\u0103 de eficien\u021b\u0103) aproape instantaneu.<\/p>\n<figure style=\"margin: 24px 0; text-align: center;\"><img decoding=\"async\" style=\"max-width: 100%; height: auto; border-radius: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); margin: 0 auto;\" src=\"https:\/\/zorin.ro\/wp-content\/uploads\/2026\/11\/02-cautare-facetata-rezultate.png\" alt=\"Rezultatele c\u0103ut\u0103rii fa\u021betate cu filtrare instantanee\"><figcaption style=\"margin-top: 8px; font-size: 0.875rem; color: #778899; font-style: italic;\">Rezultatele c\u0103ut\u0103rii fa\u021betate cu filtrare instantanee<\/figcaption><\/figure>\n<h2>1. Bytes versus string: primul obstacol serios<\/h2>\n<p>Prima barier\u0103 peste care dai c\u00e2nd treci orice aplica\u021bie de la Python 2 la Python 3 este distinc\u021bia clar\u0103 dintre \u0219irurile de bytes \u0219i string-uri (<code>str<\/code>). \u00cen Python 2 puteai amesteca f\u0103r\u0103 grij\u0103 cele dou\u0103 no\u021biuni, limbajul f\u0103c\u00e2nd conversii automate \u00een spate. \u00cen Python 3, dac\u0103 \u00eencerci s\u0103 combini sau s\u0103 decodezi implicit, interpretorul se opre\u0219te imediat.<\/p>\n<p>\u00cen Tornado 4, metoda <code>get_secure_cookie()<\/code> returna un string obi\u0219nuit. \u00cen Tornado 6, aceea\u0219i metod\u0103 returneaz\u0103 un obiect de tip bytes.<\/p>\n<p>\u00cen codul vechi aveam pur \u0219i simplu:<\/p>\n<pre><code class=\"language-python\"># Versiunea veche (Python 2)\r\ndef get_cart(self):\r\n    cookie_val = self.get_secure_cookie('cart')\r\n    return json.loads(cookie_val or '{}')<\/code><\/pre>\n<p>C\u00e2nd am pornit aplica\u021bia \u00een mediul nou cu Python 3.13, co\u0219ul de cump\u0103r\u0103turi arunca erori la fiecare ad\u0103ugare de produs. Modulul <code>json.loads()<\/code> din versiunile recente de Python refuz\u0103 s\u0103 accepte anumi\u021bi bytes nevalida\u021bi. Dac\u0103 valoarea era goal\u0103, se returna <code>None<\/code>, iar opera\u021biunea e\u0219ua direct cu o eroare de tip <code>TypeError<\/code>.<\/p>\n<p>Rezolvarea a cerut o decodare explicit\u0103 cu bloc de protec\u021bie:<\/p>\n<pre><code class=\"language-python\"># Versiunea modernizat\u0103 (Python 3.13)\r\ndef get_cart(self):\r\n    try:\r\n        val = self.get_secure_cookie('cart')\r\n        if isinstance(val, bytes):\r\n            val = val.decode('utf-8')\r\n        return json.loads(val or '{}')\r\n    except Exception as e:\r\n        logging.error(\"Eroare la parsarea JSON a cosului: %s\", e)\r\n        return {}<\/code><\/pre>\n<p>Aceea\u0219i situa\u021bie am \u00eent\u00e2lnit-o la identificarea utilizatorilor conecta\u021bi \u00een panoul administrativ. Cookie-ul de sesiune trebuia convertit explicit din bytes \u00een string \u00eenainte de a fi transmis ca parametru numeric \u00eentr-o interogare SQL.<\/p>\n<h2>2. Salvarea formularelor: crearea unui adaptor MultiDict pentru WTForms<\/h2>\n<p>O mare b\u0103taie de cap a fost gestionarea formularelor de comand\u0103. \u00cen varianta ini\u021bial\u0103 se folosea pachetul <code>wtforms-tornado<\/code>. Odat\u0103 cu trecerea la WTForms 3, biblioteca veche a cr\u0103pat \u00een totalitate din mai multe motive:<\/p>\n<ul>\n<li>Clasa <code>TextField<\/code> a fost \u0219tears\u0103 din WTForms \u00een favoarea denumirii mai clare <code>StringField<\/code>.<\/li>\n<li>Validarea <code>Required<\/code> a devenit <code>DataRequired<\/code>.<\/li>\n<li>Dic\u021bionarul de argumente <code>self.request.arguments<\/code> din Tornado stocheaz\u0103 valorile ca liste de bytes: de exemplu, c\u00e2mpul localitate sosea sub forma <code>[b'Bucuresti']<\/code>.<\/li>\n<\/ul>\n<p>C\u00e2nd formularul \u00eencerca s\u0103 citeasc\u0103 datele trimise prin POST, utilizatorul vedea \u00een c\u00e2mpurile din pagin\u0103 textul bizar <code>b'Bucuresti'<\/code>, iar validatoarele de lungime sau caractere speciale e\u0219uau zgomotos.<\/p>\n<p>\u00cen loc s\u0103 c\u0103ut\u0103m alte libr\u0103rii externe care risc\u0103 s\u0103 fie abandonate peste c\u00e2\u021biva ani, am preferat s\u0103 scriem un adaptor curat de numai 20 de linii, direct \u00een fi\u0219ierul <code>forms.py<\/code>:<\/p>\n<pre><code class=\"language-python\">class TornadoMultiDict(object):\r\n    def __init__(self, multidict):\r\n        self.multidict = multidict\r\n\r\n    def __iter__(self):\r\n        return iter(self.multidict)\r\n\r\n    def __len__(self):\r\n        return len(self.multidict)\r\n\r\n    def __contains__(self, key):\r\n        return key in self.multidict\r\n\r\n    def getlist(self, key):\r\n        valori = self.multidict.get(key, [])\r\n        return [x.decode('utf-8') if isinstance(x, bytes) else x for x in valori]\r\n\r\nclass Form(wtforms.Form):\r\n    def __init__(self, formdata=None, obj=None, prefix='', **kwargs):\r\n        if formdata is not None and not hasattr(formdata, 'getlist'):\r\n            formdata = TornadoMultiDict(formdata)\r\n        super(Form, self).__init__(formdata, obj, prefix, **kwargs)<\/code><\/pre>\n<figure style=\"margin: 24px 0; text-align: center;\"><img decoding=\"async\" style=\"max-width: 100%; height: auto; border-radius: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); margin: 0 auto;\" src=\"https:\/\/zorin.ro\/wp-content\/uploads\/2026\/11\/03-adaptor-wtforms-tornado.png\" alt=\"Implementarea adaptorului pentru WTForms \u00een mediul nou\"><figcaption style=\"margin-top: 8px; font-size: 0.875rem; color: #778899; font-style: italic;\">Implementarea adaptorului pentru WTForms \u00een mediul nou<\/figcaption><\/figure>\n<p>Cu aceast\u0103 modificare simpl\u0103, toate formularele din magazin (date de facturare pentru persoane fizice, firme, selec\u021bie modalitate de plat\u0103 \u0219i adrese de livrare) au \u00eenceput s\u0103 func\u021bioneze perfect, f\u0103r\u0103 nicio dependen\u021b\u0103 suplimentar\u0103.<\/p>\n<h2>3. SQLAlchemy 2.0 \u0219i capcana DeferredReflection<\/h2>\n<p>Un alt punct critic a fost stratul de date. Modelele din aplica\u021bie mo\u0219tenesc clasa <code>DeferredReflection<\/code>. Acest mecanism este excelent c\u00e2nd vrei ca SQLAlchemy s\u0103 inspecteze automat structura tabelelor la pornire, f\u0103r\u0103 s\u0103 fie nevoie s\u0103 redeclari fiecare coloan\u0103 de m\u00e2n\u0103 \u00een fi\u0219ierele Python:<\/p>\n<pre><code class=\"language-python\">Base = declarative_base(cls=DeferredReflection)<\/code><\/pre>\n<p>\u00cen SQLAlchemy 1.1, lucrurile mergeau liniar. Puteai s\u0103 ai clase ORM goale \u0219i s\u0103 define\u0219ti propriet\u0103\u021bi hibride care f\u0103ceau trimitere la coloane ce urmau s\u0103 fie mapate ulterior la apelul <code>Base.prepare(engine)<\/code>.<\/p>\n<p>\u00cen SQLAlchemy 2.0, regulile jocului s-au schimbat drastic. Motorul este mult mai strict \u00een privin\u021ba verific\u0103rii tipurilor \u0219i atributelor la faza de import a modulelor.<\/p>\n<p>Dac\u0103 o clas\u0103 con\u021binea o expresie hibrid\u0103 de forma:<\/p>\n<pre><code class=\"language-python\">@pricing_rule_value.expression\r\ndef pricing_rule_value(cls):\r\n    return select(PricingRule.value).where(\r\n        PricingRule.furnizor_id == cls.furnizor_id\r\n    )<\/code><\/pre>\n<p>Interpretorul arunca o excep\u021bie fatal\u0103 chiar la pornirea serverului:<\/p>\n<pre><code class=\"language-text\">AttributeError: type object 'PricingRule' has no attribute 'furnizor_id'<\/code><\/pre>\n<p>Motivul? Expresia hibrid\u0103 este evaluat\u0103 de Python la definirea clasei, moment \u00een care <code>DeferredReflection<\/code> \u00eenc\u0103 nu rulase interogarea de inspec\u021bie a bazei de date. Clasa pur \u0219i simplu nu \u0219tia c\u0103 tabela din spate con\u021bine coloana respectiv\u0103.<\/p>\n<p>Solu\u021bia a fost s\u0103 declar\u0103m explicit \u00een model doar acele c\u00e2teva coloane care particip\u0103 direct la construirea clauzelor SQL hibride:<\/p>\n<pre><code class=\"language-python\">class PricingRule(Base):\r\n    __tablename__ = 'pricing_rule'\r\n\r\n    # Declarate explicit pentru a fi recunoscute inainte de Base.prepare()\r\n    id = Column(Integer, primary_key=True)\r\n    furnizor_id = Column(Integer, nullable=True)\r\n    p_producer = Column(String(255), nullable=True)\r\n    p_size = Column(String(255), nullable=True)\r\n    p_sezon = Column(Integer, nullable=True)\r\n    value = Column(Numeric(10, 2), nullable=True)\r\n\r\n    @pricing_rule_value.expression\r\n    def pricing_rule_value(cls):\r\n        return select(PricingRule.value).where(and_(\r\n            or_(PricingRule.furnizor_id == cls.furnizor_id, PricingRule.furnizor_id == None),\r\n            or_(PricingRule.p_sezon == Product.p_sezon, PricingRule.p_sezon == None)\r\n        )).order_by(desc(PricingRule.priority)).limit(1)<\/code><\/pre>\n<p>Tot aici am cur\u0103\u021bat apelurile vechi <code>select([coloana])<\/code> care fuseser\u0103 complet eliminate \u00een noul ORM, \u00eenlocuindu-le cu apelul standard <code>select(coloana)<\/code>.<\/p>\n<figure style=\"margin: 24px 0; text-align: center;\"><img decoding=\"async\" style=\"max-width: 100%; height: auto; border-radius: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); margin: 0 auto;\" src=\"https:\/\/zorin.ro\/wp-content\/uploads\/2026\/11\/04-sqlalchemy-deferred-reflection.png\" alt=\"Modelele ORM adaptate pentru cerin\u021bele stricte din SQLAlchemy 2.0\"><figcaption style=\"margin-top: 8px; font-size: 0.875rem; color: #778899; font-style: italic;\">Modelele ORM adaptate pentru cerin\u021bele stricte din SQLAlchemy 2.0<\/figcaption><\/figure>\n<h2>4. Ce faci c\u00e2nd biblioteca de Excel refuz\u0103 s\u0103 mai citeasc\u0103 fi\u0219iere noi?<\/h2>\n<p>\u00cen fiecare diminea\u021b\u0103, devreme, un script de sincronizare descarc\u0103 actualiz\u0103rile de stoc de la depozitele partenere. Unul dintre cataloage sose\u0219te sub form\u0103 de tabel Excel cu zeci de mii de r\u00e2nduri \u00een format <code>xlsx<\/code>.<\/p>\n<p>\u00cen stack-ul original se folosea biblioteca <code>xlrd<\/code>. Totul mergea ceas p\u00e2n\u0103 c\u00e2nd, \u00eencep\u00e2nd cu versiunea 2.0, autorii xlrd au decis s\u0103 elimine definitiv suportul pentru xlsx, p\u0103str\u00e2nd doar suportul pentru formatul arhaic xls, din motive de securitate.<\/p>\n<p>Dac\u0103 \u00eencercai s\u0103 deschizi catalogul \u00een noul virtualenv, primeai sec:<\/p>\n<pre><code class=\"language-text\">XLRDError: Excel xlsx file; not supported<\/code><\/pre>\n<p>Scriptul de import con\u021binea sute de linii de parsare matematic\u0103 bazate pe API-ul specific xlrd: accesa <code>sheet.nrows<\/code> \u0219i apela <code>sheet.cell(row, col).value<\/code>. Rescrierea \u00eentregului flux pe biblioteca modern\u0103 <code>openpyxl<\/code> ar fi \u00eensemnat ore bune de refactorizare \u0219i riscul de a gre\u0219i indicii coloanelor.<\/p>\n<p>Am recurs la un model clasic de programare: duck typing printr-un adaptor transparent. Am scris o clas\u0103 minuscul\u0103 peste openpyxl care respect\u0103 aceea\u0219i interfa\u021b\u0103 pe care o a\u0219tepta vechiul script:<\/p>\n<pre><code class=\"language-python\">class OpenpyxlWork:\r\n    def __init__(self, sheet):\r\n        self.sheet = sheet\r\n\r\n    def nrows(self):\r\n        return self.sheet.max_row\r\n\r\n    def cell(self, row, col):\r\n        # openpyxl incepe numaratoarea de la 1, in timp ce xlrd o incepea de la 0\r\n        return self.sheet.cell(row=row + 1, column=col + 1).value<\/code><\/pre>\n<p>\u00cen loc s\u0103 modific\u0103m zeci de func\u021bii complexe, am schimbat doar ini\u021bializarea fi\u0219ierului: am \u00eenc\u0103rcat workbook-ul cu openpyxl \u0219i l-am \u00eempachetat \u00een clasa <code>OpenpyxlWork<\/code>. Scriptul a continuat s\u0103 ruleze f\u0103r\u0103 s\u0103 observe vreo diferen\u021b\u0103.<\/p>\n<h2>5. Mici detalii de sintax\u0103 care pot opri produc\u021bia<\/h2>\n<p>Pe l\u00e2ng\u0103 bibliotecile mari, migrarea de la Python 2 la 3 aduce o colec\u021bie de mici obstacole sintactice care scap\u0103 la o privire superficial\u0103.<\/p>\n<h3>Tuple unpacking \u00een func\u021bii lambda \/ anonime<\/h3>\n<p>\u00cen Python 2 era permis s\u0103 scrii:<\/p>\n<pre><code class=\"language-python\"># Permis in Python 2, eroare de sintaxa in Python 3\r\nproducatori = filter(lambda (k, v): k == 'marca', filtre_aplicate)<\/code><\/pre>\n<p>\u00cen Python 3, unpacking-ul parametrilor \u00een lambda a fost eliminat din limbaj, gener\u00e2nd o eroare <code>SyntaxError<\/code>. Codul a fost rescris folosind indexare direct\u0103:<\/p>\n<pre><code class=\"language-python\">producatori = list(filter(lambda x: x[0] == 'marca', filtre_aplicate))<\/code><\/pre>\n<h3>Generatorii din template-urile Tornado<\/h3>\n<p>\u00cen Python 2, func\u021biile <code>filter()<\/code> \u0219i <code>zip()<\/code> returnau direct liste. \u00cen Python 3, ele returneaz\u0103 generatoare ce pot fi parcurse o singur\u0103 dat\u0103. C\u00e2nd template-ul HTML \u00eencerca s\u0103 verifice lungimea listei de filtre sau s\u0103 o parcurg\u0103 de dou\u0103 ori, randarea paginii cr\u0103pa:<\/p>\n<pre><code class=\"language-text\">TypeError: object of type 'filter' has no len()<\/code><\/pre>\n<p>Solu\u021bia a fost convertirea lor explicit\u0103 \u00een liste ori de c\u00e2te ori sunt transmise c\u0103tre motorul de template-uri.<\/p>\n<h2>Arhitectura de produc\u021bie pe Debian 13<\/h2>\n<p>Dup\u0103 ce tot codul a fost testat \u0219i adus la zi, l-am instalat pe un server curat, configurat robust pentru trafic sus\u021binut.<\/p>\n<figure style=\"margin: 24px 0; text-align: center;\"><img decoding=\"async\" style=\"max-width: 100%; height: auto; border-radius: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); margin: 0 auto;\" src=\"https:\/\/zorin.ro\/wp-content\/uploads\/2026\/11\/05-arhitectura-productie.png\" alt=\"Schema fluxului de produc\u021bie pe server\"><figcaption style=\"margin-top: 8px; font-size: 0.875rem; color: #778899; font-style: italic;\">Schema fluxului de produc\u021bie pe server<\/figcaption><\/figure>\n<ol style=\"padding-left: 20px; line-height: 1.6;\">\n<li style=\"margin-bottom: 10px;\"><strong>Nginx 1.26<\/strong>: Preia conexiunile securizate pe portul 443, gestioneaz\u0103 certificatele Let&#8217;s Encrypt prin TLS 1.3 \u0219i serve\u0219te direct fi\u0219ierele statice (imagini de produs, stiluri CSS, scripturi frontend).<\/li>\n<li style=\"margin-bottom: 10px;\"><strong>Tornado 6.5<\/strong>: Ruleaz\u0103 ca serviciu systemd independent pe portul local 8888. Datorit\u0103 naturii asincrone, un singur proces Python gestioneaz\u0103 sute de conexiuni concurente f\u0103r\u0103 s\u0103 consume cantit\u0103\u021bi mari de memorie RAM.<\/li>\n<li style=\"margin-bottom: 10px;\"><strong>Whoosh Search Index<\/strong>: Men\u021bine un index binar compact pe stocarea rapid\u0103 SSD. Orice c\u0103utare dup\u0103 dimensiuni (de exemplu: 205\/55 R16) este rezolvat\u0103 \u00een mai pu\u021bin de 35 de milisecunde.<\/li>\n<li style=\"margin-bottom: 10px;\"><strong>MariaDB 11.8<\/strong>: P\u0103streaz\u0103 comenzile clien\u021bilor, istoricul pre\u021burilor \u0219i regulile comerciale, conect\u00e2ndu-se local prin socket Unix cu laten\u021b\u0103 zero.<\/li>\n<\/ol>\n<h2>Ce am \u00eenv\u0103\u021bat din aceast\u0103 experien\u021b\u0103<\/h2>\n<p><strong>Nu rescrie dac\u0103 po\u021bi moderniza.<\/strong> Aplica\u021biile mature au \u00een spate ani de ajust\u0103ri fine, excep\u021bii tratate pentru date primite haotic \u0219i optimiz\u0103ri specifice domeniului. O rescriere complet\u0103 arunc\u0103 la co\u0219 toat\u0103 aceast\u0103 experien\u021b\u0103 acumulat\u0103.<\/p>\n<p><strong>P\u0103streaz\u0103 frontend-ul intact dac\u0103 \u00ee\u0219i face treaba.<\/strong> Utilizatorii magazinului sunt interesa\u021bi s\u0103 g\u0103seasc\u0103 repede anvelopa dorit\u0103, s\u0103 compare clasa de consum \u0219i s\u0103 plaseze comanda \u00een trei pa\u0219i. Faptul c\u0103 interfa\u021ba folose\u0219te HTML curat, Bootstrap \u0219i jQuery bine organizat \u00eenseamn\u0103 c\u0103 paginile se deschid instant pe orice telefon, f\u0103r\u0103 framework-uri greoaie care s\u0103 blocheze procesorul.<\/p>\n<p><strong>Construie\u0219te adaptoare, nu rescrie logica de business.<\/strong> C\u00e2nd o bibliotec\u0103 extern\u0103 \u00ee\u0219i schimb\u0103 interfa\u021ba sau dispare, un adaptor mic de tip duck typing \u00ee\u021bi permite s\u0103 salvezi sute de linii de cod func\u021bional.<\/p>\n<p>Magazinul ruleaz\u0103 ast\u0103zi stabil pe cel mai nou sistem de operare, consum\u0103 sub 300 MB de memorie RAM \u0219i este preg\u0103tit pentru \u00eenc\u0103 un deceniu de func\u021bionare f\u0103r\u0103 \u00eentreruperi.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Cum am modernizat un magazin online cu peste 11.000 de anvelope active de la Python 2.7 la Python 3.13, rezolv\u00e2nd incompatibilit\u0103\u021bile din Tornado, SQLAlchemy \u0219i bibliotecile de Excel f\u0103r\u0103 o rescriere de la zero.<\/p>\n","protected":false},"author":1,"featured_media":53,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[3],"tags":[8,7,4,6,5],"_links":{"self":[{"href":"https:\/\/zorin.ro\/index.php\/wp-json\/wp\/v2\/posts\/58"}],"collection":[{"href":"https:\/\/zorin.ro\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/zorin.ro\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/zorin.ro\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/zorin.ro\/index.php\/wp-json\/wp\/v2\/comments?post=58"}],"version-history":[{"count":4,"href":"https:\/\/zorin.ro\/index.php\/wp-json\/wp\/v2\/posts\/58\/revisions"}],"predecessor-version":[{"id":62,"href":"https:\/\/zorin.ro\/index.php\/wp-json\/wp\/v2\/posts\/58\/revisions\/62"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/zorin.ro\/index.php\/wp-json\/wp\/v2\/media\/53"}],"wp:attachment":[{"href":"https:\/\/zorin.ro\/index.php\/wp-json\/wp\/v2\/media?parent=58"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/zorin.ro\/index.php\/wp-json\/wp\/v2\/categories?post=58"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/zorin.ro\/index.php\/wp-json\/wp\/v2\/tags?post=58"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}