Pengembangan di monorepositori. Laporan Yandex

Nama saya Azat Razetdinov, saya sudah berada di Yandex selama 12 tahun, saya mengelola layanan pengembangan antarmuka di Y. Real Estate. Hari ini saya ingin berbicara tentang monorepositori. Jika Anda hanya memiliki satu repositori di tempat kerja - selamat, Anda sudah tinggal dalam satu repositori. Sekarang tentang mengapa orang lain membutuhkannya.



Menurut Marina Pereskokova, kepala layanan pengembangan API Yandex.Map, kakek saya menanam monorepa, dan monorepa tumbuh sangat besar.

- Kami di Yandex mencoba berbagai cara untuk bekerja dengan beberapa layanan dan memerhatikan - segera setelah Anda memiliki lebih dari satu layanan, bagian-bagian yang tak terelakkan mulai muncul: model, utilitas, alat, potongan kode, templat, komponen. Pertanyaannya adalah: di mana harus meletakkan semua ini? Tentu saja, Anda dapat menyalin-menempel, kami dapat melakukannya, tetapi saya menginginkannya dengan indah.

Kami bahkan mencoba entitas seperti eksternal SVN bagi mereka yang ingat. Kami mencoba git submodules. Kami mencoba paket npm ketika muncul. Tapi semua ini entah bagaimana panjang, atau semacamnya. Anda mendukung paket apa pun, menemukan kesalahan, melakukan koreksi. Kemudian Anda perlu merilis versi baru, melalui layanan, meng-upgrade ke versi ini, memeriksa apakah semuanya berfungsi, menjalankan tes, menemukan kesalahan, kembali ke repositori perpustakaan, memperbaiki kesalahan, merilis versi baru, melalui layanan, memperbarui dan sebagainya lingkaran. Itu hanya berubah menjadi rasa sakit.



Kemudian kami berpikir tentang apakah kami harus berkumpul dalam satu repositori. Ambil semua layanan dan perpustakaan kami, transfer dan kembangkan dalam satu repositori. Ada banyak keuntungan. Saya tidak mengatakan bahwa pendekatan ini ideal, tetapi dari sudut pandang perusahaan dan bahkan departemen dari beberapa kelompok, keuntungan yang signifikan muncul.

Bagi saya pribadi, yang paling penting adalah atomicity dari commit, bahwa sebagai pengembang, saya dapat memperbaiki perpustakaan, mem-bypass semua layanan, membuat perubahan, menjalankan tes, memverifikasi bahwa semuanya berfungsi, dorong ke master, dan semua ini dengan satu perubahan. Tidak perlu membangun kembali, menerbitkan, memperbarui apa pun.

Tetapi jika semuanya begitu baik, mengapa belum semua orang pindah ke repositori mono? Tentu saja, ada juga kelemahannya.



Menurut Marina Pereskokova, kepala layanan pengembangan API Yandex.Map, kakek saya menanam monorepa, dan monorepa telah tumbuh besar, besar. Ini adalah fakta, bukan lelucon. Jika Anda mengumpulkan banyak layanan dalam satu repositori, itu pasti akan tumbuh. Dan jika kita berbicara tentang git, yang mengeluarkan semua file plus seluruh sejarahnya untuk seluruh keberadaan kode Anda, ini adalah ruang disk yang agak besar.

Masalah kedua adalah injeksi ke master. Anda menyiapkan permintaan kumpulan, melewati ulasan, Anda siap untuk menggabungkannya. Dan ternyata seseorang berhasil mengungguli Anda dan Anda perlu menyelesaikan konflik. Anda menyelesaikan konflik, sekali lagi siap untuk dituangkan, dan sekali lagi Anda tidak punya waktu. Masalah ini sedang diselesaikan, ada menggabungkan sistem antrian, ketika robot khusus mengotomatiskan pekerjaan ini, permintaan jalur, mencoba untuk menyelesaikan konflik, jika bisa. Jika dia tidak bisa, dia memanggil penulis. Namun, masalah seperti itu ada. Ada beberapa solusi yang meningkatkannya, tetapi Anda harus mengingatnya.

Ini adalah poin teknis, tetapi ada juga yang bersifat organisasi. Misalkan Anda memiliki beberapa tim yang membuat beberapa layanan berbeda. Ketika mereka pindah ke satu repositori, tanggung jawab mereka mulai terkikis. Karena mereka membuat rilis, diluncurkan dalam produksi - sesuatu pecah. Kami memulai tanya jawab. Ternyata itu adalah pengembang dari tim lain yang telah melakukan sesuatu pada kode umum, kami menariknya, melepaskannya, tidak melihatnya, semuanya rusak. Dan tidak jelas siapa yang bertanggung jawab. Penting untuk memahami dan menggunakan semua metode yang mungkin: tes unit, tes integrasi, linter - segala sesuatu yang mungkin untuk mengurangi masalah ini dari pengaruh satu kode pada semua layanan lainnya.

Menariknya, siapa lagi selain Yandex dan pemain lain yang menggunakan repositori mono? Cukup banyak orang. Ini adalah React, Jest, Babel, Ember, Meteor, Angular. Orang-orang mengerti - lebih mudah, lebih murah, lebih cepat untuk mengembangkan dan menerbitkan paket-paket npm dari satu repositori daripada dari beberapa repositori kecil. Hal yang paling menarik adalah seiring dengan proses ini, alat untuk bekerja dengan monorepositori mulai berkembang. Hanya tentang mereka dan saya ingin bicara.

Semuanya dimulai dengan membuat monorepositori. Alat ujung depan paling terkenal di dunia untuk ini disebut lerna.



Cukup buka repositori Anda, jalankan npx lerna init, ia akan mengajukan beberapa pertanyaan sugestif dan menambahkan beberapa entitas ke copy pekerjaan Anda. Entitas pertama adalah konfigurasi lerna.json, yang menunjukkan setidaknya dua bidang: versi ujung ke ujung dari semua paket Anda dan lokasi paket Anda di sistem file. Secara default, semua paket ditambahkan ke folder paket, tetapi Anda dapat mengonfigurasinya sesuka Anda, Anda bahkan dapat menambahkannya ke root, lerna juga dapat mengambilnya.

Langkah selanjutnya adalah bagaimana menambahkan repositori Anda ke repositori mono, bagaimana cara mentransfernya?

Apa yang ingin kita capai? Kemungkinan besar, Anda sudah memiliki semacam repositori, dalam hal ini A dan B.



Ini adalah dua layanan, masing-masing dalam repositori sendiri, dan kami ingin mentransfernya ke mono-repositori baru di folder paket, lebih disukai dengan riwayat komit, sehingga Anda dapat membuat kesalahan git, log git, dan sebagainya.



Ada alat impor lerna untuk ini. Anda cukup menentukan lokasi repositori Anda, dan lerna mentransfernya ke monorepo Anda. Pada saat yang sama, dia, pertama, mengambil daftar semua komit, memodifikasi setiap komit, mengubah path ke file dari root ke paket / nama_paket, dan menerapkannya satu demi satu, menindihnya dalam repositori mono Anda. Bahkan, setiap komit menyiapkan, mengubah jalur file di dalamnya. Intinya, lerna melakukan git magic untuk Anda. Jika Anda membaca kode sumber, hanya ada perintah git yang dieksekusi dalam urutan tertentu.

Ini cara pertama. Ini memiliki kelemahan: jika Anda bekerja di perusahaan di mana ada proses produksi, di mana orang sudah menulis semacam kode, dan Anda akan menerjemahkannya ke dalam monorep, kecil kemungkinan Anda akan melakukannya dalam satu hari. Anda perlu mencari tahu, mengkonfigurasi, memverifikasi bahwa semuanya dimulai, tes. Tetapi orang tidak punya pekerjaan, mereka terus melakukan sesuatu.



Untuk transisi yang lebih mulus ke mono-rap, ada alat seperti git subtree. Ini adalah hal yang lebih canggih, tetapi pada saat yang sama berasal dari git, yang memungkinkan Anda untuk tidak hanya mengimpor repositori individual ke repositori mono dengan semacam awalan, tetapi juga bertukar perubahan bolak-balik. Yaitu, tim yang membuat layanan dapat dengan mudah dikembangkan lebih lanjut dalam repositori sendiri, sementara Anda dapat menarik perubahannya melalui tarikan git subtree, melakukan perubahan sendiri dan mendorongnya kembali melalui git subtree push. Dan hidup seperti ini dalam masa transisi selama yang Anda suka.

Dan ketika Anda telah mengatur semuanya, memeriksa apakah semua tes sedang berjalan, penyebaran berfungsi, seluruh CI / CD dikonfigurasikan, Anda dapat mengatakan bahwa inilah saatnya untuk melanjutkan. Untuk masa transisi, solusi hebat, saya sarankan.

Ya, kami memindahkan repositori kami ke dalam satu mono-repositori, tetapi di mana sihirnya? Tetapi kami ingin menyoroti bagian-bagian umum dan entah bagaimana menggunakannya. Dan untuk ini ada mekanisme "ketergantungan mengikat". Apa yang mengikat ketergantungan? Ada alat bootstrap lerna, sebuah perintah yang mirip dengan npm install, jalankan npm install di semua paket Anda.



Tapi itu belum semuanya. Selain itu, dia mencari dependensi internal. Anda dapat menggunakan yang lain dalam satu paket di dalam repositori Anda. Misalnya, jika Anda memiliki paket A, yang tergantung pada Jest dalam kasus ini, ada paket B, yang tergantung pada Jest dan paket A. Jika paket A adalah alat yang umum, komponen yang umum, maka paket B adalah layanan yang memilikinya menggunakan.

Lerna mendefinisikan dependensi internal seperti itu dan secara fisik menggantikan dependensi ini dengan tautan simbolis pada sistem file.





Setelah Anda menjalankan lerna bootstrap, tepat di dalam folder node_modules, alih-alih folder fisik A, tautan simbolik muncul yang mengarah ke folder dengan paket A. Ini sangat nyaman karena Anda dapat mengedit kode di dalam paket A dan segera memeriksa hasilnya dalam paket B , jalankan tes, integrasi, unit, apa pun yang Anda inginkan. Pengembangan sangat disederhanakan, Anda tidak perlu lagi memasang kembali paket A, menerbitkan, menghubungkan paket B. Hanya diperbaiki di sini, diperiksa di sana.

Harap dicatat bahwa jika Anda melihat folder node_modules, dan di sana dan di sana bercanda, kami telah menduplikasi modul yang diinstal. Secara umum, ini adalah waktu yang cukup lama ketika Anda memulai lerna bootstrap, tunggu sampai semuanya berhenti, karena ada banyak jenis pekerjaan berulang, dependensi duplikat diperoleh di setiap paket.

Untuk mempercepat pemasangan dependensi, mekanisme untuk meningkatkan dependensi digunakan. Idenya sangat sederhana: Anda dapat mengambil dependensi umum ke root node_modules.



Jika Anda menentukan opsi --hoist (ini merupakan peningkatan dari bahasa Inggris), maka hampir semua dependensi hanya akan pindah ke root node_modules. Dan itu hampir selalu berhasil. Noda diatur sedemikian rupa sehingga jika dia belum menemukan ketergantungan pada levelnya, dia mulai mencari satu level lebih tinggi, jika tidak ada, level lain lebih tinggi dan seterusnya. Hampir tidak ada yang berubah. Tetapi pada kenyataannya, kami mengambil dan mendupuplikasi dependensi kami, mentransfer dependensi ke root.

Pada saat yang sama, lerna cukup pintar. Jika ada konflik, misalnya, jika paket A menggunakan Jest versi 1, dan paket B menggunakan versi 2, maka salah satu dari mereka akan muncul, dan yang kedua akan tetap pada levelnya. Ini kira-kira apa yang sebenarnya dilakukan npm di dalam folder normal node_modules, ia juga mencoba untuk mendeduplikasi dependensi dan secara maksimal membawanya ke root.

Sayangnya, keajaiban ini tidak selalu berhasil, terutama dengan alat, dengan Babel, dengan Jest. Sering terjadi bahwa ia mulai, karena Jest memiliki sistem sendiri untuk menyelesaikan modul, Noda mulai ketinggalan, melempar kesalahan. Khusus untuk kasus-kasus seperti itu ketika alat tidak mengatasi dependensi yang telah pergi ke root, ada opsi nohoist, yang memungkinkan Anda untuk menunjukkan bahwa paket-paket ini tidak ditransfer ke root, biarkan di tempat.



Jika Anda menentukan --nohoist = jest, maka semua dependensi kecuali jest akan masuk ke root, dan jest akan tetap pada level paket. Tidak heran saya memberikan contoh seperti itu - itu lelucon yang memiliki masalah dengan perilaku ini, dan nohoist membantu dengan ini.

Kelebihan lain dari pemulihan ketergantungan:



Jika sebelum itu Anda memiliki paket-lock.json terpisah untuk setiap layanan, untuk setiap paket, maka ketika Anda hoyed, semuanya bergerak naik, dan satu-satunya paket-lock.json tersisa. Ini nyaman dari sudut pandang menuangkan ke master, menyelesaikan konflik. Setelah semua orang terbunuh, dan hanya itu.

Tetapi bagaimana lerna mencapai ini? Dia cukup agresif dengan npm. Ketika Anda menentukan hoist, ia mengambil package.json Anda di root, mencadangkannya, menggantikan yang lain, menggabungkan semua dependensi Anda ke dalamnya, menjalankan npm install, hampir semuanya dimasukkan ke dalam root. Kemudian package.json sementara ini menghapus, mengembalikan milik Anda. Jika setelah itu Anda menjalankan perintah dengan npm, misalnya, hapus npm, npm tidak akan mengerti apa yang terjadi, mengapa semua dependensi tiba-tiba muncul di root. Lerna melanggar level abstraksi, dia merangkak ke alat, yang berada di bawah levelnya.

Orang-orang dari Yarn adalah orang pertama yang memperhatikan masalah ini dan berkata: apa yang kami siksa, biarkan kami melakukan semuanya untuk Anda secara asli, sehingga semuanya di luar kotak berfungsi.



Benang sudah dapat melakukan hal yang sama di luar kotak: mengikat dependensi, jika ia melihat bahwa paket B tergantung pada paket A, ia akan membuat symlink untuk Anda, gratis. Dia tahu cara meningkatkan dependensi, apakah secara default, semuanya menambahkan hingga ke root. Seperti lerna, ia dapat meninggalkan satu-satunya benang. Buka di akar repositori. Semua orang lain mengaitkannya. Biarkan Anda tidak perlu lagi.



Ini dikonfigurasi dengan cara yang sama. Sayangnya, benang mengasumsikan bahwa semua pengaturan ditambahkan ke package.json, saya tahu ada orang yang mencoba mengambil semua pengaturan alat dari sana, hanya menyisakan minimum. Sayangnya, benang belum belajar untuk menentukan ini di file lain, hanya package.json. Ada dua opsi baru, satu baru dan satu wajib. Karena diasumsikan bahwa repositori root tidak akan pernah mempublikasikan, benang memerlukan private = true untuk ditentukan di sana.

Tetapi pengaturan untuk ruang kerja disimpan dalam kunci yang sama. Pengaturannya sangat mirip dengan pengaturan lerna, ada bidang paket tempat Anda menentukan lokasi paket Anda, dan ada opsi nohoist, sangat mirip dengan opsi nohoist di lerna. Cukup tentukan pengaturan ini dan dapatkan struktur yang sama seperti di lerna. Semua dependensi umum pergi ke root, dan yang ditentukan dalam kunci nohoist tetap pada level mereka.



Bagian terbaiknya adalah lerna dapat bekerja dengan benang dan mengambil pengaturannya. Cukup menentukan dua bidang di lerna.json, lerna akan segera mengerti bahwa Anda menggunakan benang, masuk ke package.json, dapatkan semua pengaturan dari sana dan bekerja dengannya. Kedua alat ini sudah saling mengenal dan bekerja sama.



Dan mengapa dukungan belum dilakukan pada npm jika begitu banyak perusahaan besar menggunakan repositori mono?


Tautan dari slide

Mereka mengatakan bahwa semuanya akan terjadi, tetapi dalam versi ketujuh. Dukungan dasar di ketujuh, diperpanjang - di kedelapan. Posting ini dirilis sebulan yang lalu, tetapi pada saat yang sama, tanggalnya masih belum diketahui kapan jam ketujuh akan dirilis. Kami menunggu dia akhirnya mengejar ketinggalan dengan benang.

Ketika Anda memiliki beberapa layanan dalam satu mono-repositori, muncul pertanyaan bagaimana cara mengelolanya agar tidak masuk ke setiap folder, bukan menjalankan perintah? Ada operasi besar untuk ini.





Benang memiliki perintah ruang kerja benang, diikuti oleh nama paket dan nama perintah. Karena benang dari kotak, tidak seperti npm, dapat melakukan ketiga hal: menjalankan perintahnya sendiri, menambahkan ketergantungan pada lelucon, menjalankan skrip dari package.json, seperti tes, dan juga dapat menjalankan file yang dapat dieksekusi dari folder node_modules / .bin. Dia akan mengajar untuk Anda dengan bantuan heuristik, ia akan mengerti apa yang Anda inginkan. Sangat nyaman untuk menggunakan ruang kerja benang untuk operasi titik pada satu paket.

Ada perintah serupa yang memungkinkan Anda untuk mengeksekusi perintah pada semua paket yang Anda miliki.



Tunjukkan hanya perintah Anda dengan semua argumen.



Dari pro, sangat mudah untuk menjalankan tim yang berbeda. Dari minus, misalnya, tidak mungkin menjalankan perintah shell. Misalkan saya ingin menghapus semua folder modul simpul, saya tidak dapat menjalankan thread workspace run rm.
Tidak mungkin menentukan daftar paket, misalnya, saya ingin menghapus ketergantungan hanya dalam dua paket, hanya satu per satu atau secara terpisah.

Yah, dia jatuh pada kesalahan pertama. Jika saya ingin menghapus ketergantungan dari semua paket - dan pada kenyataannya, hanya dua dari mereka yang memilikinya, tetapi saya tidak ingin memikirkan di mana itu, tetapi saya hanya ingin menghapusnya - maka benang tidak akan membiarkannya, maka benang tidak akan membiarkannya, akan jatuh pada situasi pertama di mana paket ini tidak dalam dependensi. Ini sangat tidak nyaman, kadang-kadang Anda ingin mengabaikan kesalahan, jalankan melalui semua paket.



Lerna memiliki toolkit yang jauh lebih menarik, ada dua perintah run dan exec yang terpisah. Run dapat mengeksekusi skrip dari package.json, dan tidak seperti benang, ia dapat memfilter semuanya dengan paket, Anda dapat menentukan --scope, Anda dapat menggunakan tanda bintang, gumpalan, semuanya cukup universal. Anda dapat menjalankan operasi ini secara paralel, Anda dapat mengabaikan kesalahan melalui saklar --no-bail.



Exec sangat mirip. Tidak seperti benang, ini memungkinkan Anda untuk tidak hanya menjalankan file yang dapat dieksekusi dari node_modules.bin, tetapi juga menjalankan perintah shell sembarang. Misalnya, Anda dapat menghapus node_modules atau menjalankan beberapa make, apa pun yang Anda inginkan. Dan opsi yang sama didukung.



Alat yang sangat nyaman, beberapa plus. Ini adalah kasus ketika lerna merobek benang, berada pada tingkat abstraksi yang tepat. Inilah yang dibutuhkan lerna: sederhanakan pekerjaan dengan beberapa paket di monorepe.

Dengan monoreps ada satu lagi minus. Ketika Anda memiliki CI / CD, Anda tidak dapat mengoptimalkannya. Semakin banyak layanan yang Anda miliki, semakin lama waktu yang dibutuhkan. Misalkan Anda mulai menguji semua layanan untuk setiap permintaan kumpulan, dan semakin banyak, semakin lama waktu yang dibutuhkan. Operasi selektif dapat digunakan untuk mengoptimalkan proses ini. Saya akan menyebutkan tiga cara berbeda. Dua yang pertama dapat digunakan tidak hanya di monorep, tetapi juga di proyek Anda, jika karena alasan tertentu Anda tidak menggunakan metode ini.

Yang pertama adalah tahap-lint, yang memungkinkan Anda untuk menjalankan linter, menguji, semua yang Anda inginkan, hanya ke file yang telah diubah atau akan dikomit dalam komit ini. Jalankan seluruh serat bukan pada seluruh proyek Anda, tetapi hanya pada file yang telah berubah.





Penyiapannya sangat sederhana. Masukkan lint-staged, husky, pre-commit-hooks dan katakan bahwa ketika mengubah file js apa saja, Anda perlu menjalankan eslint. Dengan demikian, pemeriksaan pra-komitmen sangat dipercepat. Apalagi jika Anda memiliki banyak layanan, repositori mono yang sangat besar. Maka menjalankan eslint pada semua file terlalu mahal, dan Anda dapat mengoptimalkan kait pra-komitmen pada serat dengan cara ini.



Jika Anda menulis tes pada Jest, itu juga memiliki alat untuk menjalankan tes secara selektif.



Opsi ini memungkinkan Anda untuk memberikan daftar file sumber dan menemukan semua tes yang satu atau lain cara mempengaruhi file-file ini. Apa yang bisa digunakan bersamaan dengan pementasan serat? Harap dicatat, di sini saya tidak menentukan semua file js, tetapi hanya sumbernya. Kami mengecualikan file js sendiri dengan tes di dalamnya, kami hanya melihat sumbernya. Kami mulai findRelatedTests dan sangat mempercepat menjalankan unit untuk precommit atau prepush, seperti yang Anda inginkan.

Dan metode ketiga dikaitkan dengan monorepositori. Ini adalah lerna, yang dapat menentukan paket mana yang telah berubah dibandingkan dengan komit dasar. Ini kemungkinan besar bukan tentang pengait, tetapi tentang CI / CD Anda: Travis atau layanan lain yang Anda gunakan.





Perintah jalankan dan exec memiliki opsi sejak, yang memungkinkan Anda untuk menjalankan perintah apa saja hanya dalam paket-paket yang telah berubah sejak beberapa jenis komit. Dalam kasus sederhana, Anda bisa menentukan penyihir jika Anda menuangkan semuanya ke dalamnya. Jika Anda ingin lebih akurat, lebih baik untuk menentukan komit dasar dari permintaan kumpulan Anda melalui alat CI / CD Anda, maka ini akan menjadi pengujian yang lebih jujur.

Karena lerna mengetahui semua dependensi di dalam paket, ia dapat mendeteksi dependensi tidak langsung juga. Jika Anda mengubah perpustakaan A, yang digunakan di perpustakaan B, yang digunakan dalam layanan C, lerna akan memahami ini. , . , C โ€” , . lerna .

, : c lerna , yarn workspaces .

, . , . . ? , , , . , , . , - . , Babel. , , . . , .

Saya ingin mengucapkan terima kasih kepada rekan-rekan saya: Misha mishanga Troshev dan Gosha Besedin. Mereka menghabiskan sedikit waktu mempelajari alat-alat yang kami ulas hari ini dan berbagi pengalaman dan pengetahuan mereka. Itu saja, terima kasih.

Source: https://habr.com/ru/post/id469021/


All Articles