Menampilkan Bendera dengan Spring

1. Ikhtisar

Dalam artikel ini, kami akan secara singkat mendefinisikan tanda fitur dan mengusulkan pendekatan yang beropini dan pragmatis untuk menerapkannya dalam aplikasi Spring Boot. Kemudian, kita akan menggali iterasi yang lebih canggih dengan memanfaatkan berbagai fitur Spring Boot.

Kami akan membahas berbagai skenario yang mungkin memerlukan penandaan fitur dan membahas kemungkinan solusi. Kami akan melakukan ini menggunakan aplikasi contoh Bitcoin Miner.

2. Bendera Fitur

Tanda Fitur - terkadang disebut pengalih fitur - adalah mekanisme yang memungkinkan kita untuk mengaktifkan atau menonaktifkan fungsionalitas tertentu dari aplikasi kita tanpa harus mengubah kode atau, idealnya, menerapkan ulang aplikasi kita.

Bergantung pada dinamika yang diperlukan oleh tanda fitur tertentu, kita mungkin perlu mengonfigurasinya secara global, per instance aplikasi, atau lebih terperinci - mungkin per pengguna atau permintaan.

Seperti banyak situasi dalam Rekayasa Perangkat Lunak, penting untuk mencoba menggunakan pendekatan yang paling lugas yang menangani masalah yang dihadapi tanpa menambahkan kerumitan yang tidak perlu.

Tanda fitur adalah alat ampuh yang, jika digunakan dengan bijak, dapat menghadirkan keandalan dan stabilitas pada sistem kami. Namun, jika disalahgunakan atau kurang terawat, mereka dapat dengan cepat menjadi sumber kerumitan dan sakit kepala.

Ada banyak skenario di mana tanda fitur bisa berguna:

Pengembangan berbasis batang dan fitur nontrivial

Dalam pengembangan berbasis trunk, terutama ketika kita ingin terus melakukan integrasi, kita mungkin mendapati diri kita tidak siap untuk merilis bagian fungsionalitas tertentu. Flag fitur bisa berguna untuk memungkinkan kami terus merilis tanpa membuat perubahan kami tersedia hingga selesai.

Konfigurasi khusus lingkungan

Kami mungkin menemukan diri kami membutuhkan fungsionalitas tertentu untuk mengatur ulang DB kami untuk lingkungan pengujian E2E.

Alternatifnya, kita mungkin perlu menggunakan konfigurasi keamanan yang berbeda untuk lingkungan non-produksi dari yang digunakan di lingkungan produksi.

Karenanya, kami dapat memanfaatkan tanda fitur untuk mengubah pengaturan yang tepat di lingkungan yang tepat.

Pengujian A / B

Merilis beberapa solusi untuk masalah yang sama dan mengukur dampaknya adalah teknik menarik yang dapat kami terapkan menggunakan tanda fitur.

Pelepasan kenari

Saat menerapkan fitur baru, kami mungkin memutuskan untuk melakukannya secara bertahap, dimulai dengan sekelompok kecil pengguna, dan memperluas penerapannya saat kami memvalidasi kebenaran perilakunya. Flag fitur memungkinkan kita mencapai ini.

Di bagian berikut, kami akan mencoba memberikan pendekatan praktis untuk menangani skenario yang disebutkan di atas.

Mari kita uraikan berbagai strategi untuk menampilkan penandaan, dimulai dengan skenario paling sederhana untuk kemudian beralih ke penyiapan yang lebih terperinci dan lebih kompleks.

3. Bendera Fitur Tingkat Aplikasi

Jika kita perlu menangani salah satu dari dua kasus penggunaan pertama, tanda fitur tingkat aplikasi adalah cara sederhana untuk membuat semuanya berfungsi.

Sebuah tanda fitur sederhana biasanya akan melibatkan sebuah properti dan beberapa konfigurasi berdasarkan nilai properti itu.

3.1. Fitur Flags Menggunakan Profil Spring

Di Spring kita bisa memanfaatkan profil. Dengan mudah, profil memungkinkan kami untuk mengkonfigurasi kacang tertentu secara selektif. Dengan beberapa konstruksi di sekitarnya, kami dapat dengan cepat membuat solusi sederhana dan elegan untuk tanda fitur tingkat aplikasi.

Anggaplah kita sedang membangun sistem penambangan BitCoin. Perangkat lunak kami sudah dalam produksi, dan kami ditugaskan untuk membuat algoritme penambangan eksperimental yang ditingkatkan.

Di JavaConfig kami, kami dapat membuat profil komponen kami:

@Configuration public class ProfiledMiningConfig { @Bean @Profile("!experimental-miner") public BitcoinMiner defaultMiner() { return new DefaultBitcoinMiner(); } @Bean @Profile("experimental-miner") public BitcoinMiner experimentalMiner() { return new ExperimentalBitcoinMiner(); } }

Kemudian, dengan konfigurasi sebelumnya, kami hanya perlu menyertakan profil kami untuk ikut serta dalam fungsi baru kami. Ada banyak cara untuk mengonfigurasi aplikasi kami secara umum dan mengaktifkan profil pada khususnya. Demikian pula, ada pengujian utilitas untuk membuat hidup kita lebih mudah.

Selama sistem kita cukup sederhana, kita kemudian dapat membuat konfigurasi berbasis lingkungan untuk menentukan tanda fitur mana yang akan diterapkan dan mana yang diabaikan.

Bayangkan kita memiliki UI baru berdasarkan kartu, bukan tabel, bersama dengan penambang eksperimental sebelumnya.

Kami ingin mengaktifkan kedua fitur tersebut di lingkungan penerimaan (UAT) kami. Kita bisa membuat file application-uat.yml :

spring: profiles: include: experimental-miner,ui-cards # More config here

Dengan file sebelumnya, kita hanya perlu mengaktifkan profil UAT di lingkungan UAT untuk mendapatkan serangkaian fitur yang diinginkan.

Penting juga untuk memahami cara memanfaatkan spring.profiles.include. Dibandingkan dengan spring.profiles.active, yang pertama memungkinkan kami menyertakan profil dengan cara aditif.

Dalam kasus kami, kami ingin profil uat juga menyertakan eksperimental-miner dan ui-cards .

3.2. Tanda Fitur Menggunakan Properti Kustom

Profil adalah cara yang bagus dan sederhana untuk menyelesaikan pekerjaan. Namun, kami mungkin memerlukan profil untuk tujuan lain. Atau mungkin, kita mungkin ingin membangun infrastruktur tanda fitur yang lebih terstruktur.

Untuk skenario ini, properti kustom mungkin merupakan opsi yang diinginkan.

Mari tulis ulang contoh kita sebelumnya dengan memanfaatkan @ConditionalOnProperty dan namespace kita :

@Configuration public class CustomPropsMiningConfig { @Bean @ConditionalOnProperty( name = "features.miner.experimental", matchIfMissing = true) public BitcoinMiner defaultMiner() { return new DefaultBitcoinMiner(); } @Bean @ConditionalOnProperty( name = "features.miner.experimental") public BitcoinMiner experimentalMiner() { return new ExperimentalBitcoinMiner(); } }

Contoh sebelumnya dibangun di atas konfigurasi bersyarat Spring Boot dan mengonfigurasi satu komponen atau lainnya, bergantung pada apakah properti disetel ke true atau false (atau dihilangkan sama sekali).

The result is very similar to the one in 3.1, but now, we have our namespace. Having our namespace allows us to create meaningful YAML/properties files:

#[...] Some Spring config features: miner: experimental: true ui: cards: true #[...] Other feature flags

Also, this new setup allows us to prefix our feature flags – in our case, using the features prefix.

It might seem like a small detail, but as our application grows and complexity increases, this simple iteration will help us keep our feature flags under control.

Let's talk about other benefits of this approach.

3.3. Using @ConfigurationProperties

As soon as we get a prefixed set of properties, we can create a POJO decorated with @ConfigurationProperties to get a programmatic handle in our code.

Following our ongoing example:

@Component @ConfigurationProperties(prefix = "features") public class ConfigProperties { private MinerProperties miner; private UIProperties ui; // standard getters and setters public static class MinerProperties { private boolean experimental; // standard getters and setters } public static class UIProperties { private boolean cards; // standard getters and setters } }

By putting our feature flags' state in a cohesive unit, we open up new possibilities, allowing us to easily expose that information to other parts of our system, such as the UI, or to downstream systems.

3.4. Exposing Feature Configuration

Our Bitcoin mining system got a UI upgrade which is not entirely ready yet. For that reason, we decided to feature-flag it. We might have a single-page app using React, Angular, or Vue.

Regardless of the technology, we need to know what features are enabled so that we can render our page accordingly.

Let's create a simple endpoint to serve our configuration so that our UI can query the backend when needed:

@RestController public class FeaturesConfigController { private ConfigProperties properties; // constructor @GetMapping("/feature-flags") public ConfigProperties getProperties() { return properties; } }

There might be more sophisticated ways of serving this information, such as creating custom actuator endpoints. But for the sake of this guide, a controller endpoint feels like good enough a solution.

3.5. Keeping the Camp Clean

Although it might sound obvious, once we've implemented our feature flags thoughtfully, it's equally important to remain disciplined in getting rid of them once they're no longer needed.

Feature flags for the first use case – trunk-based development and non-trivial features – are typically short-lived. This means that we're going to need to make sure that our ConfigProperties, our Java configuration, and our YAML files stay clean and up-to-date.

4. More Granular Feature Flags

Sometimes we find ourselves in more complex scenarios. For A/B testing or canary releases, our previous approach is simply not enough.

To get feature flags at a more granular level, we may need to create our solution. This could involve customizing our user entity to include feature-specific information, or perhaps extending our web framework.

Polluting our users with feature flags might not be an appealing idea for everybody, however, and there are other solutions.

As an alternative, we could take advantage of some built-in tools such as Togglz. This tool adds some complexity but offers a nice out-of-the-box solution and provides first-class integration with Spring Boot.

Togglz supports different activation strategies:

  1. Username: Flags associated with specific users
  2. Gradual rollout: Flags enabled for a percentage of the user base. This is useful for Canary releases, for example, when we want to validate the behavior of our features
  3. Release date: We could schedule flags to be enabled at a certain date and time. This might be useful for a product launch, a coordinated release, or offers and discounts
  4. Client IP: Flagged features based on clients IPs. These might come in handy when applying the specific configuration to specific customers, given they have static IPs
  5. Server IP: In this case, the IP of the server is used to determine whether a feature should be enabled or not. This might be useful for canary releases too, with a slightly different approach than the gradual rollout – like when we want to assess performance impact in our instances
  6. ScriptEngine: We could enable feature flags based on arbitrary scripts. This is arguably the most flexible option
  7. System Properties: We could set certain system properties to determine the state of a feature flag. This would be quite similar to what we achieved with our most straightforward approach

5. Summary

In this article, we had a chance to talk about feature flags. Additionally, we discussed how Spring could help us achieve some of this functionality without adding new libraries.

Kami mulai dengan menjelaskan bagaimana pola ini dapat membantu kami dalam beberapa kasus penggunaan umum.

Selanjutnya, kami membuat beberapa solusi sederhana menggunakan alat bantu Spring dan Spring Boot out-of-the-box. Dengan itu, kami menghasilkan konstruksi penandaan fitur yang sederhana namun kuat.

Di bawah, kami membandingkan beberapa alternatif. Beralih dari solusi yang lebih sederhana dan kurang fleksibel ke pola yang lebih canggih, meskipun lebih kompleks.

Terakhir, kami secara singkat memberikan beberapa pedoman untuk membangun solusi yang lebih kuat. Ini berguna saat kami membutuhkan tingkat perincian yang lebih tinggi.