Sebelum ada Maven, biasanya ketika ada dependency tambahan, kita akan menambahkan Jar file dari dependency tersebut secara manual ke dalam project kita. Bayangkan, jika dependency tersebut digunakan di banyak module lainnya, berarti kita harus copy manual satu-persatu dong. Belum lagi saat ada perubahan Jar, berarti kita harus manual copy satu-persatu.
Maven adalah tools untuk management module dependency dan build Java project secara otomatis milik Apache Foundation. Maven menggunakan XML file untuk konfigurasi yang dinamakan POM (Project Object Model). Dengan Maven, kita cukup deklarasi dependency yang ingin digunakan pada modul pada pom.xml tanpa harus copy jar manual satu-persatu. Maven akan menyimpan semua jar dependency tersebut ke dalam local cache, jadi semua dependency direferensikan dari tempat yang sama tanpa harus copy manual. Jika di local cache (foler .m2/repository/) belum ada, maka maven akan melakukan download dari private repository (jika dikonfigurasikan) atau Maven Central Repository (default repository). Hasil download tersebut disimpan pada local cache, jadi setelah download sekali, selanjutnya setiap pemakaian akan diarahkan ke dependency di local cache tersebut. Mau nambahin dependency tinggal cari script-nya di mvnrepository.com lalu copy paste di pom.xml, ga perlu repot-repot download atau copy jar secara manual.
Pada Maven, kita bisa menentukan scope dependency yang kita gunakan pada module. Scope disini membatasi visibilitas dari dependency tersebut terhadap module yang kita digunakan. Untuk dapat melihat gambaran dependency pada module lewat IntelliJ bisa dengan klik kanan pada pom.xml dan pilih diagrams lalu show diagrams. Nanti keliatan alur dependency dan dapat di-trace dimana aja conflict dependency-nya.
1. Compile
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
<scope>compile</scope>
</dependency>
Compile adalah default scope pada Maven. Bila kita tidak menulis scope apapun pada dependency, berarti scope compile yang di-apply secara otomatis. Compile artinya dependency tersebut tersedia saat compile, test & runtime dan akan diikutsertakan pada modul lainnya (transitive). Contohnya seperti berikut:
Module Payment
<groupId>org.mymodule</groupId>
<artifactId>payment</artifactId>
<version>1.0</version>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
<scope>compile</scope>
</dependency>
</dependencies>
Module Order
<groupId>org.mymodule</groupId>
<artifactId>order</artifactId>
<version>1.0</version>
<dependencies>
<dependency>
<groupId>org.mymodule</groupId>
<artifactId>payment</artifactId>
<version>1.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
Module Order memiliki dependency dengan module Payment. Di dalam module Payment terdapat dependency Jackson dengan scope compile. Meskipun module Order tidak mendeklarasikan Jackson, tetap saja Jackson akan diikutsertakan ke dalam module Order lewat module Payment. Baik itu saat compile, runtime, maupun test. Termasuk jika misalkan ada dependency lain di dalam module Payment yang juga memiliki dependency dengan scope compile terhadap Jackson, maka akan ada lebih dari satu dependency Jackson yang diikutsertakan pada module Order nantinya. Jika terdapat perbedaan versi Jackson, maka yang diambil adalah versi dependency yang dideklarasikan paling awal.
2. Provided
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
<scope>provided</scope>
</dependency>
Provided berarti dependency tersebut hanya tersedia saat compile dan test, tapi tidak saat runtime. Contohnya pada lombok. Library Lombok hanya dibutuhkan saat compile untuk generate annotasi menjadi sebuah code. Pada saat runtime, library Lombok tidak lagi dibutuhkan. Selain itu, provided juga berguna untuk menyembunyikan dependency saat runtime untuk menghindari konflik versi dependency. Agar dependency tersebut bisa digunakan oleh module lain saat runtime, maka module tersebut wajib mendeklarasikan dependency tadi di dalam pom. Contohnya seperti berikut:
Module Payment
<groupId>org.mymodule</groupId>
<artifactId>payment</artifactId>
<version>1.0</version>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
<scope>provided</scope>
</dependency>
</dependencies>
Module Order
<groupId>org.mymodule</groupId>
<artifactId>order</artifactId>
<version>1.0</version>
<dependencies>
<dependency>
<groupId>org.mymodule</groupId>
<artifactId>payment</artifactId>
<version>1.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
<scope>compile</scope>
</dependency>
</dependencies>
Pada contoh di atas, module Order memiliki dependency dengan module Payment. Module Payment memiliki dependency terhadap Jackson dengan scope provided. Kita juga wajib mendeklarasikan module Jackson dengan scope compile pada module Order. Jika kita tidak mendeklarasikan Jackson pada module Order, memang tidak akan compile-error. Melainkan akan muncul ClassNotFoundError saat dijalankan & diakses karena Jackson tidak diikutsertakan saat runtime. Untuk itu setiap module yang memiliki dependency terhadap Payment harus menyediakan Jackson juga di pom.xml module tersebut, makanya disebut "provided". Misalkan terdapat dependency lain pada module Payment yang juga memiliki dependency terhadap Jackson dengan scope provided, maka saat runtime tidak masalah, asalkan pada module Order dependency Jackson dideklarasikan. Versi yang diikutsertakan saat runtime hanya satu, yaitu versi yang dideklarasikan pada module Order. Inilah yang membedakan antara scope provided dan scope compile. Scope provided ini cocoknya untuk module yang digunakan sebagai tools, add-ons atau extension dari library. Contoh yang paling umum adalah Lombok dan Spring dependency pada Springfox Swagger2.
3. Test
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.27.0</version>
<scope>test</scope>
</dependency>
Kalau ini sudah jelas, dari namanya saja sudah bisa ditebak kalau fungsinya sudah pasti untuk testing. Ini hanya tersedia saat test dan compile. Saat runtime dependency ini tidak tersedia. Contoh dependency yang menggunakan scope test adalah JUnit, Mockito, AssertJ, dan lainnya.
4. Runtime
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.6</version>
<scope>runtime</scope>
</dependency>
Sesuai namanya, scope Runtime hanya digunakan saat runtime dan test, tapi tidak saat compile. Gw sendiri belum pernah ketemu case yang mengharuskan gw menggunakan scope ini, jadi gw juga ga terlalu berpengalaman dengan scope ini sebenarnya. Salah satu contoh yang pernah gw lihat adalah MySql Connector.
5. System
<dependency>
<groupId>org.mymodule</groupId>
<artifactId>payment</artifactId>
<version>1.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/libs/payment-1.0.jar</systemPath>
</dependency>
Berbeda dengan scope lainnya yang otomatis download dari repository, scope ini mengharuskan kita mengikutsertakan jar dependency secara manual. Di sini kita harus tambahkan juga tag system path yang mengarah ke jar dependency yang ingin dipakai. Scope ini sangat jarang dipakai, dianggap sudah usang dan tidak lagi direkomendasikan. Gw sendiri juga ga pernah menggunakan scope ini.
6. Import
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.6.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Scope ini hanya berfungsi jika disertai tag type dengan value pom. Ini bukan bagian dari transitive dependency seperti 5 scope sebelumnya, tetapi bagian dari dependency management. Scope ini menandakan bahwa dependency management yang ada pada spring-boot-dependencies juga ikut dibawa ke dalam dependency management pada module yang kita punya. Contoh yang paling umum ditemui adalah Spring Boot Dependencies.
Verdict
Itulah macam-macam scope pada Maven. Memahami macam-macam scope pada Maven penting untuk menghindari Error pada aplikasi. Dengan begitu kita lebih berhati-hati untuk menentukan langkah yang tepat saat membuat module. Selain Maven, juga ada Gradle sebagai kompetitornya. Gw sendiri pernah menggunakan Gradle dulu, tapi belum sempat mendalaminya. Lebih seringnya menggunakan Maven hingga sekarang, jadi untuk Gradle gw belum bisa share apa-apa😅.