ΠΠ°ΠΊΡΠΎ ΠΌΠΎΠΆΠ΅ Π΄Π° ΡΠ΅ ΡΠ°Π·Π±Π΅ΡΠ΅ ΠΎΡ ΠΏΡΠ΅Π΄ΠΈΡΠ½Π°ΡΠ° ΡΡΠ°ΡΠΈΡ, ΡΠ°Π±ΠΎΡΠΈΡ
ΠΏΠΎ ΡΠ°Π·Π»ΠΈΡΠ½ΠΈ ΠΏΡΠΎΠ΅ΠΊΡΠΈ. ΠΡΡΠ²ΠΈΡΠ΅ Π΄Π½ΠΈ Π² Π½ΠΎΠ²ΠΈΡ Π΅ΠΊΠΈΠΏ ΠΎΠ±ΠΈΠΊΠ½ΠΎΠ²Π΅Π½ΠΎ ΠΌΠΈΠ½Π°Π²Π°Ρ ΠΏΠΎ Π΅Π΄ΠΈΠ½ ΠΈ ΡΡΡ Π½Π°ΡΠΈΠ½: Π±Π΅ΠΊΠ΅Π½Π΄ ΡΠ°Π·ΡΠ°Π±ΠΎΡΡΠΈΠΊΡΡ ΡΡΠ΄Π° Π΄ΠΎ ΠΌΠ΅Π½ ΠΈ ΠΈΠ·Π²ΡΡΡΠ²Π° ΠΌΠ°Π³ΠΈΡΠ΅ΡΠΊΠΈ Π΄Π΅ΠΉΡΡΠ²ΠΈΡ ΠΏΠΎ ΠΈΠ½ΡΡΠ°Π»ΠΈΡΠ°Π½Π΅ΡΠΎ ΠΈ Π΄Π΅ΠΏΠ»ΠΎΡ Π½Π° ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ΡΠΎ. Docker Π΅ Π½Π΅Π·Π°ΠΌΠ΅Π½ΠΈΠΌ Π·Π° ΡΡΠΎΠ½ΡΠ΅Π½Π΄ ΡΠ°Π·ΡΠ°Π±ΠΎΡΡΠΈΡΠΈΡΠ΅, ΡΡΠΉ ΠΊΠ°ΡΠΎ Π±Π΅ΠΊΠ΅Π½Π΄ΡΡ ΡΠ΅ΡΡΠΎ Π΅ Π½Π°ΠΏΠΈΡΠ°Π½ Π½Π° ΡΠ°Π·Π»ΠΈΡΠ½ΠΈ ΡΡΠ΅ΠΊΠΎΠ²Π΅ PHP/Java/Python/C# ΠΈ ΡΡΠΎΠ½ΡΠ΅Π½Π΄ΡΡ Π½Π΅ ΡΡΡΠ±Π²Π° Π²ΡΠ΅ΠΊΠΈ ΠΏΡΡ Π΄Π° ΡΠ°Π·ΡΠ΅ΠΉΠ²Π° Π±Π΅ΠΊΠ΅Π½Π΄Π°, Π·Π° Π΄Π° ΠΈΠ½ΡΡΠ°Π»ΠΈΡΠ° ΠΈ ΡΠ°Π·Π³ΡΡΡΠ° Π²ΡΠΈΡΠΊΠΎ. Π‘Π°ΠΌΠΎ Π½Π° Π΅Π΄Π½ΠΎ ΠΌΡΡΡΠΎ Π²ΠΈΠ΄ΡΡ
ΠΊΠΎΠΌΠ±ΠΈΠ½Π°ΡΠΈΡ Docker-Jenkins Ρ ΠΏΡΠΎΠ·ΡΠ°ΡΠ΅Π½ Π΄Π΅ΠΏΠ»ΠΎΠΉ, Π»ΠΎΠ³ΠΎΠ²Π΅ ΠΈ ΠΏΡΠΈΠΊΠ°ΡΠ΅Π½ΠΈ Π°Π²ΡΠΎΠΌΠ°ΡΠΈΠ·ΠΈΡΠ°Π½ΠΈ ΡΠ΅ΡΡΠΎΠ²Π΅.
ΠΠ° Docker ΡΠ° Π½Π°ΠΏΠΈΡΠ°Π½ΠΈ ΠΌΠ½ΠΎΠ³ΠΎ ΠΏΠΎΠ΄ΡΠΎΠ±Π½ΠΈ ΡΡΠ°ΡΠΈΠΈ. Π ΡΠ°Π·ΠΈ ΡΡΠ°ΡΠΈΡ ΡΠ΅ Π³ΠΎΠ²ΠΎΡΠΈΠΌ Π·Π° ΡΠ°Π·Π³ΡΡΡΠ°Π½Π΅ΡΠΎ Π½Π° Single Page Application Ρ ΠΈΠ·ΠΏΠΎΠ»Π·Π²Π°Π½Π΅ Π½Π° VueJS/Vue Router, Π° ΡΡΡΠ²ΡΡΠ½Π°ΡΠ° ΡΠ°ΡΡ Π΅ ΠΏΠΎΠ΄ ΡΠΎΡΠΌΠ°ΡΠ° Π½Π° RESTful API Ρ NodeJS, ΠΈ ΠΊΠ°ΡΠΎ Π±Π°Π·Π° Π΄Π°Π½Π½ΠΈ ΡΠ΅ ΠΈΠ·ΠΏΠΎΠ»Π·Π²Π° MongoDB. ΠΠ° ΠΎΠΏΠΈΡΠ°Π½ΠΈΠ΅ ΠΈ ΡΡΠ°ΡΡΠΈΡΠ°Π½Π΅ Π½Π° Π½ΡΠΊΠΎΠ»ΠΊΠΎ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ-ΠΊΠΎΠ½ΡΠ΅ΠΉΠ½Π΅ΡΠ° ΡΠ΅ ΠΈΠ·ΠΏΠΎΠ»Π·Π²Π° Docker Compose.
ΠΠ°ΡΠΎ Π΅ Π½ΡΠΆΠ΅Π½ Docker
Docker ΠΏΠΎΠ·Π²ΠΎΠ»ΡΠ²Π° Π°Π²ΡΠΎΠΌΠ°ΡΠΈΠ·ΠΈΡΠ°Π½Π΅ Π½Π° ΠΏΡΠΎΡΠ΅ΡΠ° Π½Π° ΡΠ°Π·Π³ΡΡΡΠ°Π½Π΅ Π½Π° ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ΡΠΎ. Π Π°Π·ΡΠ°Π±ΠΎΡΡΠΈΠΊΡΡ Π²Π΅ΡΠ΅ Π½Π΅ ΡΡΡΠ±Π²Π° ΡΠ°ΠΌ Π΄Π° ΠΈΠ½ΡΡΠ°Π»ΠΈΡΠ° ΠΏΡΠΎΠ³ΡΠ°ΠΌΠΈ ΠΈ Π΄Π° ΡΠ΅ Π±ΠΎΡΠΈ Ρ Π½Π΅ΡΡΠ²ΠΌΠ΅ΡΡΠΈΠΌΠΎΡΡΡΠ° Π½Π° Π²Π΅ΡΡΠΈΠΈΡΠ΅ Π½Π° ΡΠ²ΠΎΡ ΠΊΠΎΠΌΠΏΡΡΡΡ. ΠΠΎΡΡΠ°ΡΡΡΠ½ΠΎ Π΅ Π΄Π° ΠΈΠ½ΡΡΠ°Π»ΠΈΡΠ° Docker ΠΈ Π΄Π° Π²ΡΠ²Π΅Π΄Π΅ 1-2 ΠΊΠΎΠΌΠ°Π½Π΄ΠΈ Π² ΠΊΠΎΠ½Π·ΠΎΠ»Π°ΡΠ°. ΠΠ°ΠΉ-ΡΠ΄ΠΎΠ±Π½ΠΎ Π΅ Π΄Π° ΡΠ΅ ΠΏΡΠ°Π²ΠΈ ΡΠΎΠ²Π° Π½Π° Linux.
ΠΠ°ΠΏΠΎΡΠ²Π°ΠΌΠ΅
ΠΠ½ΡΡΠ°Π»ΠΈΡΠ°ΠΌΠ΅ ΠΈ
Π‘ΡΡΡΠΊΡΡΡΠ° Π½Π° ΠΏΠ°ΠΏΠΊΠΈΡΠ΅
Π‘ΡΠ·Π΄Π°Π²Π°ΠΌΠ΅ 2 ΠΏΠ°ΠΏΠΊΠΈ Π·Π° ΠΊΠ»ΠΈΠ΅Π½ΡΡΠΊΠΎΡΠΎ ΠΈ ΡΡΡΠ²ΡΡΠ½ΠΎΡΠΎ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅. Π€Π°ΠΉΠ» Ρ ΡΠ°Π·ΡΠΈΡΠ΅Π½ΠΈΠ΅ .yml β ΡΠΎΠ²Π° Π΅ ΠΊΠΎΠ½ΡΠΈΠ³ Docker Compose, Π² ΠΊΠΎΠΉΡΠΎ ΡΠ΅ ΠΎΠΏΡΠ΅Π΄Π΅Π»ΡΡ ΠΈ ΡΠ²ΡΡΠ·Π²Π°Ρ ΠΊΠΎΠ½ΡΠ΅ΠΉΠ½Π΅ΡΠΈΡΠ΅ Π½Π° ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ΡΠΎ.
Docker-compose.yml:
version: "3"
services:
mongo:
container_name: mongo
hostname: mongo
image: mongo
ports:
- "27017:27017"
server:
build: server/
#command: node ./server.js #ΡΡΠΊ ΠΌΠΎΠΆΠ΅ Π΄Π° ΡΠ΅ ΠΏΡΠ΅Π·Π°ΠΏΠΈΡΠ΅ CMD ΠΎΡ Dockerfile Π² /server
ports:
- "3000:3000"
links:
- mongo
client:
build: client/
#command: http-server ./dist #ΡΡΠΊ ΠΌΠΎΠΆΠ΅ Π΄Π° ΡΠ΅ ΠΏΡΠ΅Π·Π°ΠΏΠΈΡΠ΅ CMD ΠΎΡ Dockerfile Π² /client
network_mode: host
ports:
- "8089:8089"
depends_on:
- server
Π‘ΡΠ·Π΄Π°Π²Π°ΠΌΠ΅ 3 ΡΡΠ»ΡΠ³ΠΈ Π² Docker: Π·Π° NodeJS, MongoDB ΠΈ Π·Π° ΡΡΠ°ΡΠΈΡΠ½ΠΎ ΡΡΠ΄ΡΡΠΆΠ°Π½ΠΈΠ΅ Π½Π° Vue. ΠΠ° Π΄Π° ΡΠ²ΡΡΠΆΠ΅ΠΌ ΠΊΠ»ΠΈΠ΅Π½ΡΠ° ΡΡΡ ΡΡΡΠ²ΡΡΠ° Π΅ Π΄ΠΎΠ±Π°Π²Π΅Π½ΠΎ depends on server. ΠΠ° Π²ΡΡΠ·ΠΊΠ° Π½Π° MongoDB ΡΡΡ ΡΡΡΠ²ΡΡΠ½ΠΎΡΠΎ API ΡΠ΅ ΠΈΠ·ΠΏΠΎΠ»Π·Π²Π° links mongo. Server, client, mongo β ΠΈΠΌΠ΅Π½Π°ΡΠ° Π½Π° ΡΡΠ»ΡΠ³ΠΈΡΠ΅.
ΠΠ»ΠΈΠ΅Π½Ρ Π½Π° VueJS
Π ΠΏΠ°ΠΏΠΊΠ°ΡΠ° /client ΡΡΠ΄ΡΡΠΆΠ° ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ Π½Π° VueJS. ΠΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ΡΠΎ Π΅ ΡΡΠ·Π΄Π°Π΄Π΅Π½ΠΎ Ρ ΠΏΠΎΠΌΠΎΡΡΠ° Π½Π° . ΠΡΠΈ ΠΈΠ·Π³ΡΠ°ΠΆΠ΄Π°Π½Π΅ΡΠΎ Π½Π° ΠΎΠ±ΡΠ°Π·Π° ΠΊΠ»ΠΈΠ΅Π½ΡΡΠΊΠΎΡΠΎ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ ΡΠ΅ Π±ΠΈΠ»Π΄Π²Π° Π² ΠΊΠΎΠΌΠΏΠ»Π΅ΠΊΡ ΡΡΠ°ΡΠΈΡΠ½ΠΈ ΡΠ°ΠΉΠ»ΠΎΠ²Π΅ Π² ΠΏΠ°ΠΏΠΊΠ°ΡΠ° /dist. Π Dockerfile Π΅ ΠΎΠΏΠΈΡΠ°Π½ ΠΊΠΎΠΌΠΏΠ»Π΅ΠΊΡ ΠΊΠΎΠΌΠ°Π½Π΄ΠΈ Π·Π° ΠΈΠ·Π³ΡΠ°ΠΆΠ΄Π°Π½Π΅ Π½Π° ΠΎΠ±ΡΠ°Π·Π°:
FROM node:10
WORKDIR /client
COPY ./package*.json ./
RUN npm install
RUN npm install -g http-server
COPY . .
RUN npm run build
EXPOSE 8081
CMD ["npm", "test:dev"]
ΠΠ±ΡΡΠ½Π΅ΡΠ΅ Π²Π½ΠΈΠΌΠ°Π½ΠΈΠ΅, ΡΠ΅ package.json ΡΠ΅ ΠΊΠΎΠΏΠΈΡΠ° ΠΈ ΠΈΠ½ΡΡΠ°Π»ΠΈΡΠ° ΠΎΡΠ΄Π΅Π»Π½ΠΎ ΠΎΡ ΠΎΡΡΠ°Π½Π°Π»ΠΈΡΠ΅ ΡΠ°ΠΉΠ»ΠΎΠ²Π΅ Π½Π° ΠΏΡΠΎΠ΅ΠΊΡΠ°. Π’ΠΎΠ²Π° Π΅ Π½Π°ΠΏΡΠ°Π²Π΅Π½ΠΎ Π·Π° ΠΏΡΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡΠ΅Π»Π½ΠΎΡΡ, Π·Π° Π΄Π° ΡΠ΅ ΠΊΠ΅ΡΠΈΡΠ° ΡΡΠ΄ΡΡΠΆΠ°Π½ΠΈΠ΅ΡΠΎ Π½Π° ΠΏΠ°ΠΏΠΊΠ°ΡΠ° /node_modules ΠΏΡΠΈ ΠΏΠΎΠ²ΡΠΎΡΠ½ΠΎ ΠΊΠΎΠΌΠΏΠΈΠ»ΠΈΡΠ°Π½Π΅. ΠΡΡΠΊΠ° ΠΊΠΎΠΌΠ°Π½Π΄Π° ΡΠ΅ ΠΊΠ΅ΡΠΈΡΠ° ΠΎΡΠ΄Π΅Π»Π½ΠΎ.
Π ΠΊΡΠ°Ρ, ΠΏΡΠΈ ΡΡΠ°ΡΡΠΈΡΠ°Π½Π΅ Π½Π° ΠΊΠΎΠ½ΡΠ΅ΠΉΠ½Π΅ΡΠ° ΡΠ΅ ΠΈΠ·ΠΏΡΠ»Π½ΡΠ²Π° ΠΊΠΎΠΌΠ°Π½Π΄Π°ΡΠ° npm run dev. Π’Π°Π·ΠΈ ΠΊΠΎΠΌΠ°Π½Π΄Π° Π΅ ΠΎΠΏΠΈΡΠ°Π½Π° Π² package.json:
"scripts": {
"test:dev": "http-server dist -p 8081 -c 1 --push-state"
}
ΠΠ° Π΄Π° ΡΡΠ°ΡΡΠΈΡΠ°ΡΠ΅ ΡΠ°ΠΉΠ»ΠΎΠ²Π΅ΡΠ΅ ΠΎΡ ΠΏΠ°ΠΏΠΊΠ°ΡΠ° /dist, Π³Π»ΠΎΠ±Π°Π»Π½ΠΎ ΡΠ΅ ΠΈΠ½ΡΡΠ°Π»ΠΈΡΠ° http-server, Π° Π² dev-dependencies ΠΏΠ°ΠΊΠ΅ΡΡΡ spa-http-server, Π·Π° Π΄Π° ΡΠ°Π±ΠΎΡΠΈ Vue Router ΠΏΡΠ°Π²ΠΈΠ»Π½ΠΎ. Π€Π»Π°Π³ΡΡ βpush-state ΠΏΡΠ°Π²ΠΈ ΠΏΡΠ΅Π½Π°ΡΠΎΡΠ²Π°Π½Π΅ ΠΊΡΠΌ index.html. Π€Π»Π°Π³ΡΡ -c ΡΡΡ ΡΡΠΎΠΉΠ½ΠΎΡΡ 1 ΡΠ΅ΠΊΡΠ½Π΄Π° Π΅ Π΄ΠΎΠ±Π°Π²Π΅Π½, Π·Π° Π΄Π° http-server Π½Π΅ ΠΊΠ΅ΡΠΈΡΠ° ΡΠΊΡΠΈΠΏΡΠΎΠ²Π΅. Π’ΠΎΠ²Π° Π΅ ΡΠ΅ΡΡΠΎΠ² ΠΏΡΠΈΠΌΠ΅Ρ, Π² ΡΠ΅Π°Π»Π΅Π½ ΠΏΡΠΎΠ΅ΠΊΡ Π΅ ΠΏΠΎ-Π΄ΠΎΠ±ΡΠ΅ Π΄Π° ΡΠ΅ ΠΈΠ·ΠΏΠΎΠ»Π·Π²Π° nginx.
Π Vuex store ΡΡΠ·Π΄Π°Π²Π°ΠΌΠ΅ ΠΏΠΎΠ»Π΅ apiHost: 'http://localhost:3000', ΠΊΡΠ΄Π΅ΡΠΎ Π΅ Π·Π°Π΄Π°Π΄Π΅Π½ ΠΏΠΎΡΡΡΡ Π½Π° NodeJS Api. ΠΠ»ΠΈΠ΅Π½ΡΡΠΊΠ°ΡΠ° ΡΠ°ΡΡ Π΅ Π³ΠΎΡΠΎΠ²Π°. Π‘Π΅Π³Π° Π²ΡΠΈΡΠΊΠΈ Π·Π°ΡΠ²ΠΊΠΈ ΠΎΡ ΠΊΠ»ΠΈΠ΅Π½ΡΠ° ΠΊΡΠΌ Π±Π΅ΠΊΠ΅Π½Π΄Π° ΠΎΡΠΈΠ²Π°Ρ Π½Π° ΡΠΎΠ·ΠΈ url.
NodeJS server API
Π ΠΏΠ°ΠΏΠΊΠ°ΡΠ° /server ΡΡΠ·Π΄Π°Π²Π°ΠΌΠ΅ server.js ΠΈ Dockerfile:
FROM node:10
WORKDIR /server
COPY ./package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
Π server.js ΡΠ΅ ΠΎΡΠ±Π΅Π»ΡΠ·Π²Π° url Π·Π° Π±Π°Π·Π°ΡΠ° Π΄Π°Π½Π½ΠΈ const url = 'mongodb://mongo:27017/';. Π Π°Π·ΡΠ΅ΡΠ°Π²Π°ΠΌΠ΅ ΠΊΡΠΎΡΡΠ΄ΠΎΠΌΠ΅ΠΉΠ½ΠΈ Π·Π°ΡΠ²ΠΊΠΈ ΠΎΡ ΠΊΠ»ΠΈΠ΅Π½ΡΠ°:
const clientUrl = 'http://localhost:8081';
const corsOptions = {
origin: clientUrl,
optionsSuccessStatus: 200, // Π½ΡΠΊΠΎΠΈ ΡΡΠ°ΡΠΈ Π±ΡΠ°ΡΠ·ΡΡΠΈ (IE11, ΡΠ°Π·Π»ΠΈΡΠ½ΠΈ SmartTV) ΠΌΠΎΠΆΠ΅ Π΄Π° ΠΈΠΌΠ°Ρ ΠΏΡΠΎΠ±Π»Π΅ΠΌΠΈ Ρ 204
};
app.use(cors());
app.all('*', (req, res, next) => {
res.header('Access-Control-Allow-Origin', clientUrl);
res.header('Access-Control-Allow-Headers', 'X-Requested-With');
next();
});
app.get('/getProducts', cors(corsOptions), (req, res) => {
products.getContent
.then(data => res.json(data), err => res.json(err));
});
app.get('/getUsers', cors(corsOptions), (req, res) => {
db.getUsers()
.then(data => res.json(data), err => res.json(err));
});
ΠΠ°ΠΊΠ»ΡΡΠ΅Π½ΠΈΠ΅
Π‘Π΅Π³Π° ΡΠ΅ ΠΏΡΠ΅ΠΌΠΈΠ½Π΅ΠΌ Π² Π΄ΠΈΡΠ΅ΠΊΡΠΎΡΠΈΡΡΠ° Π½Π° ΠΏΡΠΎΠ΅ΠΊΡΠ° ΠΈ ΡΠ΅ ΡΡΠ°ΡΡΠΈΡΠ°ΠΌΠ΅ docker-compose build Π·Π° ΠΈΠ·Π³ΡΠ°ΠΆΠ΄Π°Π½Π΅ Π½Π° ΠΎΠ±ΡΠ°Π·ΠΈ ΠΈ docker-compose up Π·Π° ΡΡΠ°ΡΡΠΈΡΠ°Π½Π΅ Π½Π° ΠΊΠΎΠ½ΡΠ΅ΠΉΠ½Π΅ΡΠΈ. ΠΠΎΠΌΠ°Π½Π΄Π°ΡΠ° ΡΠ΅ ΡΡΠ°ΡΡΠΈΡΠ° 3 ΠΊΠΎΠ½ΡΠ΅ΠΉΠ½Π΅ΡΠ°: server, client, mongo. ΠΠ° ΡΡΡΠ²ΡΡΠ° Π½Π° NodeJS ΠΌΠΎΠΆΠ΅ Π΄Π° Π½Π°ΡΡΡΠΎΠΈΡΠ΅ hot-reload, ΡΠ²ΡΡΠ·Π°Π½ Ρ ΠΏΠ°ΠΏΠΊΠ°ΡΠ° user. Π ΠΊΠ»ΠΈΠ΅Π½ΡΡΡ Π½Π° Π΅ΡΠ°ΠΏ ΡΠ°Π·ΡΠ°Π±ΠΎΡΠΊΠ° Π΄Π° ΡΠ΅ ΡΡΠ°ΡΡΠΈΡΠ° Π»ΠΎΠΊΠ°Π»Π½ΠΎ Ρ hot reload, ΠΊΠ°ΡΠΎ ΡΠ΅ ΠΏΡΡΠ½Π΅ ΠΎΡΠ΄Π΅Π»Π½ΠΎ server ΠΈ mongo. ΠΠ° Π΄Π° ΡΡΠ°ΡΡΠΈΡΠ°ΡΠ΅ ΠΎΡΠ΄Π΅Π»Π΅Π½ ΡΠ΅ΡΠ²ΠΈΡ, ΠΏΡΠΎΡΡΠΎ Π·Π°Π΄Π°ΠΉΡΠ΅ ΠΈΠΌΠ΅ΡΠΎ ΠΌΡ docker-compose up client. ΠΠ΅ Π·Π°Π±ΡΠ°Π²ΡΠΉΡΠ΅ ΠΏΠΎΠ½ΡΠΊΠΎΠ³Π° Π΄Π° ΠΏΡΠ°Π²ΠΈΡΠ΅ prune ΠΈ Π΄Π° ΠΈΠ·ΡΡΠΈΠ²Π°ΡΠ΅ ΠΊΠΎΠ½ΡΠ΅ΠΉΠ½Π΅ΡΠΈ (containers), ΠΌΡΠ΅ΠΆΠΈ (networks) ΠΈ ΠΎΠ±ΡΠ°Π·ΠΈ (images), Π·Π° Π΄Π° ΠΎΡΠ²ΠΎΠ±ΠΎΠ΄ΠΈΡΠ΅ ΡΠ΅ΡΡΡΡΠΈ.
Π¦ΡΠ»ΠΎΡΡΠ½ΠΈΡΡ ΠΊΠΎΠ΄ ΠΌΠΎΠΆΠ΅ Π΄Π° ΡΠ΅ Π²ΠΈΠ΄ΠΈ . ΠΡΠΎΠ΅ΠΊΡΡΡ Π²ΡΠ΅ ΠΎΡΠ΅ Π΅ Π² Π΅ΡΠ°ΠΏ Π½Π° ΡΠ°Π·ΡΠ°Π±ΠΎΡΠΊΠ°.
ΠΠ·ΡΠΎΡΠ½ΠΈΠΊ: habr.com
