socialgekon.com
  • Põhiline
  • Ux Disain
  • Planeerimine Ja Prognoosimine
  • Innovatsioon
  • Veaotsing
Tagumine Ots

Null kangelaseni: Kolvitootmise retseptid

Masinaõppeinsenerina ja arvutinägemise ekspert , Avastan end üllatavalt sageli Flaskiga API-sid ja isegi veebirakendusi. Selles postituses tahan jagada mõningaid näpunäiteid ja kasulikke retsepte täieliku tootmiseks valmis Kolbi rakenduse loomiseks.

Käsitleme järgmisi teemasid:

  1. Konfiguratsiooni juhtimine. Igal reaalses elus oleval rakendusel on konkreetsete etappidega elutsükkel - see oleks vähemalt arendus, testimine ja juurutamine. Igal etapil peaks rakenduse kood töötama veidi erinevas keskkonnas, mis nõuab erinevate seadistuste komplekti, näiteks andmebaasi ühenduse stringide, väliste API-de võtmete ja URL-ide olemasolu.
  2. Ise hostiv kolbirakendus Gunicorniga. Kuigi Flaskil on sisseehitatud veebiserver, nagu me kõik teame, ei sobi see tootmiseks ja see tuleb asetada tõelise veebiserveri taha, mis suudaks Flaskiga WSGI-protokolli kaudu suhelda. Levinud valik selleks on Gunicorn —Pythoni WSGI HTTP-server.
  3. Staatiliste failide ja puhverserveri taotlemine teenusega Nginx . Olles HTTP veebiserver, on Gunicorn omakorda rakendusserver, mis ei sobi veebiga silmitsi seisma. Sellepärast vajame Nginxi vastupidise puhverserverina ja staatiliste failide teenindamiseks. Juhul, kui peame oma rakendust mitmesse serverisse laiendama, hoolitseb Nginx ka koormuse tasakaalustamise eest.
  4. Rakenduse juurutamine Dockeri konteinerites spetsiaalses Linuxi serveris. Konteinerites kasutuselevõtt on tarkvara kujundamise oluline osa olnud juba pikka aega. Meie rakendus ei erine ja pakendatakse kenasti oma konteinerisse (tegelikult mitu konteinerit).
  5. Rakenduse jaoks PostgreSQL-i andmebaasi konfigureerimine ja juurutamine. Andmebaasi struktuuri ja migreerumisi haldab Alembic koos SQLAlchemy objekt-relatsioonilise kaardistamise pakkumine.
  6. A seadistamine Seller ülesannete järjekord pikaajaliste ülesannete käsitsemiseks. Kõik rakendused nõuavad seda lõpuks aja või arvutusmahukate protsesside - olgu see siis meilisõnumite saatmine, automaatne andmebaaside hoidmine või üleslaaditud piltide töötlemine - mahalaadimiseks veebiserveri lõimedest välistöötajatele.

Kolbi rakenduse loomine

Alustame rakenduse koodi ja varade loomisega. Pange tähele, et ma ei käsitle selles postituses õiget Flaski rakenduse struktuuri. Demorakendus koosneb lühiduse ja selguse huvides minimaalsest arvust moodulitest ja pakettidest.



Kõigepealt looge kataloogistruktuur ja lähtestage tühi Giti hoidla.

mkdir flask-deploy cd flask-deploy # init GIT repo git init # create folder structure mkdir static tasks models config # install required packages with pipenv, this will create a Pipfile pipenv install flask flask-restful flask-sqlalchemy flask-migrate celery # create test static asset echo 'Hello World!' > static/hello-world.txt

Järgmisena lisame koodi.

config / __ init__.py

Konfiguratsioonimoodulis määratleme oma pisikese konfiguratsioonihalduse raamistiku. Idee on panna rakendus käituma vastavalt seadistusele, mille on valinud APP_ENV keskkonnamuutuja, pluss lisage vajadusel ükskõik milline konfiguratsiooniseade konkreetse keskkonnamuutujaga alistama.

import os import sys import config.settings # create settings object corresponding to specified env APP_ENV = os.environ.get('APP_ENV', 'Dev') _current = getattr(sys.modules['config.settings'], '{0}Config'.format(APP_ENV))() # copy attributes to the module for convenience for atr in [f for f in dir(_current) if not '__' in f]: # environment can override anything val = os.environ.get(atr, getattr(_current, atr)) setattr(sys.modules[__name__], atr, val) def as_dict(): res = {} for atr in [f for f in dir(config) if not '__' in f]: val = getattr(config, atr) res[atr] = val return res

config / settings.py

See on komplekt konfiguratsiooniklassidest, millest ühe valib APP_ENV muutuv. Kui rakendus töötab, kood __init__.py instantsib ühe nendest klassidest, välistades välja väärtused konkreetsete keskkonnamuutujatega, kui need on olemas. Kolvi ja selleri konfiguratsiooni hiljem lähtestamisel kasutame lõplikku konfiguratsiooni objekti.

class BaseConfig(): API_PREFIX = '/api' TESTING = False DEBUG = False class DevConfig(BaseConfig): FLASK_ENV = 'development' DEBUG = True SQLALCHEMY_DATABASE_URI = 'postgresql://db_user: [email protected] :5432/flask-deploy' CELERY_BROKER = 'pyamqp://rabbit_user: [email protected] //' CELERY_RESULT_BACKEND = 'rpc://rabbit_user: [email protected] //' class ProductionConfig(BaseConfig): FLASK_ENV = 'production' SQLALCHEMY_DATABASE_URI = 'postgresql://db_user: [email protected] :5432/flask-deploy' CELERY_BROKER = 'pyamqp://rabbit_user: [email protected] //' CELERY_RESULT_BACKEND = 'rpc://rabbit_user: [email protected] //' class TestConfig(BaseConfig): FLASK_ENV = 'development' TESTING = True DEBUG = True # make celery execute tasks synchronously in the same process CELERY_ALWAYS_EAGER = True

ülesanded / __ init__.py

Ülesannete pakett sisaldab selleri initsialiseerimiskoodi. Konfiguratsioonipaketti, millel on kõik seadistused initsialiseerimise ajal juba mooduli tasemel kopeeritud, kasutatakse selleri seadistamisobjekti värskendamiseks juhul, kui meil on tulevikus mõni selleri spetsiifiline säte - näiteks ajastatud toimingud ja töötajate ajalõpp.

from celery import Celery import config def make_celery(): celery = Celery(__name__, broker=config.CELERY_BROKER) celery.conf.update(config.as_dict()) return celery celery = make_celery()

ülesanded / seller_worker.py

See moodul on vajalik selleri töötaja käivitamiseks ja initsialiseerimiseks, mis töötab eraldi Dockeri konteineris. See lähtestab rakenduse Flask konteksti, et tal oleks juurdepääs rakendusega samale keskkonnale. Kui see pole vajalik, saab need jooned ohutult eemaldada.

from app import create_app app = create_app() app.app_context().push() from tasks import celery

api / __ init__.py

Edasi läheb API pakett, mis määratleb REST API, kasutades paketti Flask-Restful. Meie rakendus on lihtsalt demo ja sellel on ainult kaks lõpp-punkti:

  • /process_data - Alustab selleritöötajale pika operatsiooni ja tagastab uue ülesande ID.
  • /tasks/ - tagastab ülesande oleku ülesande ID järgi.
import time from flask import jsonify from flask_restful import Api, Resource from tasks import celery import config api = Api(prefix=config.API_PREFIX) class TaskStatusAPI(Resource): def get(self, task_id): task = celery.AsyncResult(task_id) return jsonify(task.result) class DataProcessingAPI(Resource): def post(self): task = process_data.delay() return {'task_id': task.id}, 200 @celery.task() def process_data(): time.sleep(60) # data processing endpoint api.add_resource(DataProcessingAPI, '/process_data') # task status endpoint api.add_resource(TaskStatusAPI, '/tasks/')

mudelid / __ init__.py

Nüüd lisame SQLAlchemy mudeli User jaoks objekt ja andmebaasimootori lähtestuskood. User Meie demorakendus ei kasuta objekti mingil tähenduslikul viisil, kuid me vajame seda, et veenduda andmebaasi migreerimiste toimimises ja SQLAlchemy-Flaski integreerimise õiges seadistamises.

import uuid from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy() class User(db.Model): id = db.Column(db.String(), primary_key=True, default=lambda: str(uuid.uuid4())) username = db.Column(db.String()) email = db.Column(db.String(), unique=True)

Pange tähele, kuidas UUID luuakse vaikeväljendina automaatselt objekti ID-na.

app.py

Lõpuks loome peamise Flaski rakendusfaili.

from flask import Flask logging.basicConfig(level=logging.DEBUG, format='[%(asctime)s]: {} %(levelname)s %(message)s'.format(os.getpid()), datefmt='%Y-%m-%d %H:%M:%S', handlers=[logging.StreamHandler()]) logger = logging.getLogger() def create_app(): logger.info(f'Starting app in {config.APP_ENV} environment') app = Flask(__name__) app.config.from_object('config') api.init_app(app) # initialize SQLAlchemy db.init_app(app) # define hello world page @app.route('/') def hello_world(): return 'Hello, World!' return app if __name__ == '__main__': app = create_app() app.run(host='0.0.0.0', debug=True)

Siin me oleme:

  • Põhilogimise seadistamine õiges vormingus koos aja, taseme ja protsessi ID-ga
  • Rakenduse Flask loomise funktsiooni määratlemine API initsialiseerimise ja „Tere, maailm!” Abil lehele
  • Rakenduse arendusajal töötamiseks sisestuspunkti määratlemine

wsgi.py

Samuti vajame Gunicorniga Flaski rakenduse käitamiseks eraldi moodulit. Sellel on ainult kaks rida:

from app import create_app app = create_app()

Rakenduse kood on valmis. Meie järgmine samm on Dockeri konfiguratsiooni loomine.

Dokkeri konteinerite ehitamine

Meie rakendus nõuab töötamiseks mitut Dockeri konteinerit:

  1. Rakenduskonteiner malllehtede serveerimiseks ja API lõpp-punktide kuvamiseks. Need kaks funktsiooni on mõistlik lavastuses jagada, kuid meil pole demorakenduses ühtegi malllehte. Konteiner töötab Gunicorni veebiserveriga, mis suhtleb kolbiga WSGI-protokolli kaudu.
  2. Selleri töötaja konteiner pikkade ülesannete täitmiseks. See on sama rakenduse konteiner, kuid Gunicorni asemel Celery käivitamiseks kohandatud käsu abil.
  3. Seller võitis konteineri - sarnane ülaltooduga, kuid korrapärase ajakava järgi kutsutud ülesannete jaoks, näiteks nende kasutajate kontode eemaldamiseks, kes pole kunagi oma e-posti aadressi kinnitanud.
  4. JänesMQ konteiner. Seller nõuab töötajate ja rakenduse vahel suhtlemiseks ning ülesannete tulemuste salvestamiseks sõnumivahendajat. RabbitMQ on tavaline valik, kuid võite kasutada ka Redist või Kafkat.
  5. Andmebaasikonteiner koos PostgreSQL-iga.

Loomulik viis mitme konteineri hõlpsaks haldamiseks on Docker Compose'i kasutamine. Kuid kõigepealt peame looma Dockerfile'i, et ehitada meie rakenduse jaoks konteineripilt. Pange see projekti kataloogi.

FROM python:3.7.2 RUN pip install pipenv ADD . /flask-deploy WORKDIR /flask-deploy RUN pipenv install --system --skip-lock RUN pip install gunicorn[gevent] EXPOSE 5000 CMD gunicorn --worker-class gevent --workers 8 --bind 0.0.0.0:5000 wsgi:app --max-requests 10000 --timeout 5 --keep-alive 5 --log-level info

See fail juhendab Dockerit:

  • Installige kõik sõltuvused Pipenv abil
  • Lisage konteinerisse rakenduste kaust
  • Paljastage hostile TCP-port 5000
  • Määrake konteineri vaikekäivituskäskuks Gunicorn kõne

Arutleme rohkem, mis juhtub viimasel real. See käitab Gunicorni, täpsustades töölisklassi järgmiselt tuulutati . Gevent on kerge samaaegne lib, mis on mõeldud ühistööga multitegumtöötlemiseks. See annab sisend- / väljundühendustega koormustele märkimisväärse jõudluse kasvu, pakkudes paremat protsessori kasutamist võrreldes OS-i ennetava niitide mitmeülesandega. --workers parameeter on töötajate protsesside arv. See on hea mõte seadistada võrdseks tuumade arvuga serveris.

Kui meil on rakenduse konteiner Dockerfile, saame luua docker-compose.yml fail, mis määrab kõik konteinerid, mida rakendus vajab.

version: '3' services: broker-rabbitmq: image: 'rabbitmq:3.7.14-management' environment: - RABBITMQ_DEFAULT_USER=rabbit_user - RABBITMQ_DEFAULT_PASS=rabbit_password db-postgres: image: 'postgres:11.2' environment: - POSTGRES_USER=db_user - POSTGRES_PASSWORD=db_password migration: build: . environment: - APP_ENV=${APP_ENV} command: flask db upgrade depends_on: - db-postgres api: build: . ports: - '5000:5000' environment: - APP_ENV=${APP_ENV} depends_on: - broker-rabbitmq - db-postgres - migration api-worker: build: . command: celery worker --workdir=. -A tasks.celery --loglevel=info environment: - APP_ENV=${APP_ENV} depends_on: - broker-rabbitmq - db-postgres - migration api-beat: build: . command: celery beat -A tasks.celery --loglevel=info environment: - APP_ENV=${APP_ENV} depends_on: - broker-rabbitmq - db-postgres - migration

Määrasime järgmised teenused:

  • broker-rabbitmq - RabbitMQ sõnumivahendaja konteiner. Ühenduse mandaadid on määratletud keskkonnamuutujate abil
  • db-postgres - PostgreSQL-i konteiner ja selle volikirjad
  • migration - Rakenduse konteiner, mis teostab andmebaasi üleviimise rakendusega Flask-Migrate ja väljub. API konteinerid sõltuvad sellest ja töötavad pärast seda.
  • api - peamine rakenduse konteiner
  • api-worker ja api-beat - konteinerid, mis töötavad selleritöötajatega API-lt saadud ülesannete ja kavandatud ülesannete jaoks

Iga rakenduse konteiner saab ka APP_ENV muutuja väärtusest docker-compose up käsk.

Kui kõik rakenduse varad on meil valmis, pange need GitHubi, mis aitab meil koodi serverisse juurutada.

git add * git commit -a -m 'Initial commit' git remote add origin [email protected] :your-name/flask-deploy.git git push -u origin master

Serveri seadistamine

Meie kood on nüüd GitHubis ja üle jääb vaid serveri esmane seadistamine ja rakenduse juurutamine. Minu puhul on server AWS-i eksemplar, milles töötab AMI Linux. Muude Linuxi versioonide puhul võivad juhised veidi erineda. Samuti eeldan, et serveril on juba väline IP-aadress, DNS on konfigureeritud sellele IP-le osutava kirjega A ja domeenile väljastatakse SSL-sertifikaadid.

Turvalisuse näpunäide: Ärge unustage oma hostikonsoolis lubada HTTP (S) liikluse jaoks porte 80 ja 44, SSH porti 22 (või | | + + | |) kasutades ja sulgeda välimine juurdepääs kõigile teistele portidele! Tehke kindlasti sama ka IPv6 protokoll!

Sõltuvuste installimine

Esiteks vajame koodi tõmbamiseks Nginxit ja Dockerit ning Gitit. Logime sisse SSH kaudu ja kasutame nende installimiseks paketihaldurit.

iptables

Nginxi seadistamine

Järgmine samm on Nginxi konfigureerimine. Peamine sudo yum install -y docker docker-compose nginx git konfiguratsioonifail on tihtipeale hea. Siiski kontrollige kindlasti, kas see sobib teie vajadustega. Loome oma rakenduse jaoks uue konfiguratsioonifaili kaustas nginx.conf kausta. Tipptasemel konfiguratsioonil on direktiiv, mis hõlmab kõiki conf.d faile sellest.

.conf

Siin on Nginxi kolbi saidi konfiguratsioonifail, kaasas patareid. Sellel on järgmised omadused:

  1. SSL on konfigureeritud. Teil peaks olema oma domeeni jaoks kehtivad sertifikaadid, nt tasuta Krüpteerime tunnistus.
  2. cd /etc/nginx/conf.d sudo vim flask-deploy.conf taotlused suunatakse aadressile www.your-site.com
  3. HTTP-päringud suunatakse HTTPS-pordi turvaliseks muutmiseks.
  4. Tagurpidi puhverserver on konfigureeritud edastama päringuid kohalikule porti 5000.
  5. Staatilisi faile teenindab Nginx kohalikust kaustast.
your-site.com

Pärast faili redigeerimist käivitage server { listen 80; listen 443; server_name www.your-site.com; # check your certificate path! ssl_certificate /etc/nginx/ssl/your-site.com/fullchain.crt; ssl_certificate_key /etc/nginx/ssl/your-site.com/server.key; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers HIGH:!aNULL:!MD5; # redirect to non-www domain return 301 https://your-site.com$request_uri; } # HTTP to HTTPS redirection server { listen 80; server_name your-site.com; return 301 https://your-site.com$request_uri; } server { listen 443 ssl; # check your certificate path! ssl_certificate /etc/nginx/ssl/your-site.com/fullchain.crt; ssl_certificate_key /etc/nginx/ssl/your-site.com/server.key; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers HIGH:!aNULL:!MD5; # affects the size of files user can upload with HTTP POST client_max_body_size 10M; server_name your-site.com; location / { include /etc/nginx/mime.types; root /home/ec2-user/flask-deploy/static; # if static file not found - pass request to Flask try_files $uri @flask; } location @flask { add_header 'Access-Control-Allow-Origin' '*' always; add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS'; add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization'; add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range'; proxy_read_timeout 10; proxy_send_timeout 10; send_timeout 60; resolver_timeout 120; client_body_timeout 120; # set headers to pass request info to Flask proxy_set_header Host $http_host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $remote_addr; proxy_redirect off; proxy_pass http://127.0.0.1:5000$uri; } } ja vaadake, kas seal on vigu.

GitHubi mandaatide seadistamine

Hea tava on projekti ja CI / CD-süsteemi juurutamiseks eraldi VCS-konto „juurutamine“. Nii ei riski te oma konto mandaatide avaldamisega. Projektihoidla täiendavaks kaitsmiseks võite selle konto õigusi piirata ainult kirjutuskaitstud juurdepääsuga. GitHubi hoidla jaoks vajate selleks organisatsiooni kontot. Demorakenduse juurutamiseks loome lihtsalt avaliku võtme serverisse ja registreerime selle GitHubis, et pääseda juurde meie projektile iga kord mandaate sisestamata.

Uue SSH-võtme loomiseks käivitage:

sudo nginx -s reload

Seejärel logige sisse GitHubi ja lisage oma avalik võti alates cd ~/.ssh ssh-keygen -b 2048 -t rsa -f id_rsa.pub -q -N '' -C 'deploy' konto seadetes.

Rakenduse juurutamine

Viimased sammud on üsna lihtsad - peame hankima GitHubilt rakenduskoodi ja käivitama kõik konteinerid rakendusega Docker Compose.

~/.ssh/id_rsa.pub

Võib olla hea mõte cd ~ git clone https://github.com/your-name/flask-deploy.git git checkout master APP_ENV=Production docker-compose up -d välja jätta (mis käivitab konteineri eraldatud režiimis) esimeseks jooksuks, et näha iga konteineri väljundit otse terminalis ja kontrollida võimalikke probleeme. Teine võimalus on kontrollida iga üksikut konteinerit -d -ga pärast. Vaatame, kas kõik meie konteinerid töötavad docker logs -ga

pilt_alt_tekst

Suurepärane. Kõik viis konteinerit on töökorras. Docker Koostage määratud konteinerite nimed automaatselt vastavalt teenuses docker-compose.yml määratud teenusele. Nüüd on aeg lõpuks testida, kuidas kogu konfiguratsioon töötab! Parim on testid käivitada välisest arvutist, veendumaks, et serveris on õiged võrguseaded.

docker ps.

See on kõik. Meil on AWS-i eksemplaris töötava rakenduse minimalistlik, kuid täielikult tootmisvalmis konfiguratsioon. Loodetavasti aitab see teil tõsielulist rakendust kiiresti üles ehitama hakata ja vältida levinumaid vigu! Kogu kood on saadaval GitHubi hoidlas .

Järeldus

Selles artiklis käsitlesime mõnda parimat praktikat kolvi rakenduse struktureerimisel, konfigureerimisel, pakendamisel ja tootmisse juurutamisel. See on väga suur teema, mida pole võimalik ühes ajaveebipostituses täielikult kajastada. Siin on loetelu olulistest küsimustest, mida me ei käsitlenud:

See artikkel ei hõlma järgmist:

  • Pidev integreerimine ja pidev juurutamine
  • Automaatne testimine
  • Logide saatmine
  • API jälgimine
  • Rakenduse laiendamine mitmesse serverisse
  • Volituste kaitse lähtekoodis

Kuid saate teada, kuidas seda teha, kasutades mõnda muud selle ajaveebi suurepärast ressurssi. Näiteks metsaraie uurimiseks vt Pythoni logimine: põhjalik õpetus või üldise ülevaate CI / CD ja automatiseeritud testimise kohta vt Kuidas luua tõhusat esialgset juurutamistorustikku . Jätan nende rakendamise harjutuseks teile, lugeja.

Täname lugemast!

Põhitõdede mõistmine

Mis on kolbi rakendus?

Flaski rakendus on Pythoni rakendus, mis on loodud veebi jaoks koos Flaski teegiga.

Kumb on parem, kolb või Django?

Mõlemad raamistikud sobivad mitmesuguste veebiga seotud ülesannete täitmiseks. Kolbi kasutatakse sageli veebiteenuste ehitamiseks, mis ei ole täieõiguslikud veebisaidid, ja see on tuntud oma paindlikkuse poolest.

Mis on Gunicorn?

Gunicorn on Pythoni WSGI HTTP-server. Selle tavaliseks kasutamiseks on Flaski või Django Pythoni rakenduste serveerimine WSGI-liidese kaudu tootmises.

Mis on seller ja miks seda koos kolbiga kasutada?

Veebiserverid, nagu Flask, ei sobi pikaajaliste ülesannete täitmiseks, näiteks videotöötluseks. Seller on ülesannete järjekord selliste ülesannete mugavaks ja asünkroonseks käsitsemiseks. Ülesande andmed salvestatakse toetatud tagumises salvestusmootoris, näiteks RabbitMQ või Redis.

Miks kasutada Docker Compose'i?

Docker Compose on Dockeri jaoks mugav tööriist, mis võimaldab teenuseid määratledes töötada konteineritega kõrgemal abstraktsioonitasemel. Samuti käsitletakse konteinerite käivitamise ja peatamise tavalisi stsenaariume.

Miks on meil vaja kolbi jaoks Nginxi veebiserverit?

Kuigi Gunicorn sobib hästi rakendusserveriks, ei ole turvalisuse kaalutlustel ja veebipäringute käsitlemise piirangute tõttu mõistlik lasta Internetil silmitsi seista. Levinud muster on Nginxi kasutamine pöördproxyna Gunicorni ees teenusevaheliste taotluste suunamiseks.

Millal on PostgreSQL hea valik?

PostgreSQL on küps suure jõudlusega relatsiooniline andmebaasimootor, millel on palju pistikprogramme ja teeke kõigi suuremate programmeerimiskeelte jaoks. See on ideaalne valik, kui teie projekt, hoolimata sellest, kas see on suur või väike, vajab lihtsalt andmebaasi. Lisaks on see avatud lähtekoodiga ja tasuta.

Scala vs Java: Miks peaksin Scalat õppima?

Tehnoloogia

Scala vs Java: Miks peaksin Scalat õppima?
Kasvav kasv: tehke selle avatud lähtekoodiga oma kohordianalüüs

Kasvav kasv: tehke selle avatud lähtekoodiga oma kohordianalüüs

Andmeteadus Ja Andmebaasid

Lemmik Postitused
Kuidas veenda e-kaubanduse ostjaid UX-disaini abil
Kuidas veenda e-kaubanduse ostjaid UX-disaini abil
Sissejuhatus PHP 7-sse: mis on uut ja mis kadunud
Sissejuhatus PHP 7-sse: mis on uut ja mis kadunud
Mitme põlvkonna tööjõu võimu vabastamine, 1. osa: kas vanusel on tähtsust?
Mitme põlvkonna tööjõu võimu vabastamine, 1. osa: kas vanusel on tähtsust?
Tööstuse ärkamine: madratsitööstuse häired
Tööstuse ärkamine: madratsitööstuse häired
Sügav sukeldumine tugevdamise õppesse
Sügav sukeldumine tugevdamise õppesse
 
Lemmikloomade pildistamine iPhone'is: kuidas teha kõige armsamaid lemmikloomafotosid
Lemmikloomade pildistamine iPhone'is: kuidas teha kõige armsamaid lemmikloomafotosid
Juhend npm: paketihaldur Node.js
Juhend npm: paketihaldur Node.js
Juhend: kuidas tõhusat UX-i uuringut läbi viia
Juhend: kuidas tõhusat UX-i uuringut läbi viia
Monoreposi juhend esiotsa koodi jaoks
Monoreposi juhend esiotsa koodi jaoks
Flutter juhendaja: kuidas luua oma esimene Flutter App
Flutter juhendaja: kuidas luua oma esimene Flutter App
Kategooriad
Disaineri EluKasumlikkus Ja TõhususJaotatud VõistkonnadPlaneerimine Ja PrognoosimineInvestorid Ja RahastamineFinantsprotsessidladustamineTöö TulevikUx DisainBrändikujundus

© 2023 | Kõik Õigused Kaitstud

socialgekon.com