Help me bij het implementeren van een feature flag systeem in mijn Capacitor app
description
Door een feature flag systeem te implementeren, kun je de flexibiliteit van je app vergroten, nieuwe functies testen met geselecteerde gebruikers en de algehele gebruikerservaring verbeteren zonder frequente updates.
prompt
try_prompt
Help me een feature flag systeem te maken voor mijn Capacitor app waarmee ik functies dynamisch kan in- of uitschakelen zonder nieuwe code te implementeren. Leg uit hoe ik de feature flags kan instellen, hun staten beheren en ervoo ... more
generate_helper
...
ai_answers
provider: openai
model: gpt-4.1-nano
Het implementeren van een feature flag systeem in je Capacitor app stelt je in staat om functies dynamisch in- of uit te schakelen zonder nieuwe code te deployen. Hier is een overzicht van hoe je dit kan aanpakken, inclusief codevoorbeelden en best practices:
1. Keuze van een backend service
Gebruik een externe service voor feature flags, zoals LaunchDarkly, Firebase Remote Config, of een eigen API. Voor eenvoud en controle kun je ook een eenvoudige API maken die configuraties levert.
2. Structuur en beheer van feature flags
Definieer je flags in een centrale database of configuratiebestand. Bijvoorbeeld:
```json
{
"auth": {
"enabled": true
},
"contentSharing": {
"enabled": false
}
}
```
3. Opzetten van een API endpoint
Maak een API die de huidige status van de flags levert, bijvoorbeeld:
```json
GET /api/feature-flags
{
"auth": true,
"contentSharing": false
}
```
4. In je Capacitor app:
- Haal de flags op bij het starten van de app
- Sla ze lokaal op (bijvoorbeeld in `localStorage` of een veilige opslag)
- Gebruik ze om functies aan of uit te zetten
Voorbeeld code:
```typescript
// TypeScript voorbeeld
interface FeatureFlags {
auth: boolean;
contentSharing: boolean;
}
async function fetchFeatureFlags(): Promise<FeatureFlags> {
const response = await fetch('https://mijnapi.com/api/feature-flags');
if (!response.ok) {
// fallback of default flags
return { auth: true, contentSharing: true };
}
const flags: FeatureFlags = await response.json();
localStorage.setItem('featureFlags', JSON.stringify(flags));
return flags;
}
async function getFeatureFlags(): Promise<FeatureFlags> {
const stored = localStorage.getItem('featureFlags');
if (stored) {
return JSON.parse(stored);
}
return fetchFeatureFlags();
}
// Gebruik in je app
async function initFeatures() {
const flags = await getFeatureFlags();
if (flags.auth) {
// Activeer authenticatiefunctie
} else {
// Verberg of deactiveer authenticatie
}
if (flags.contentSharing) {
// Activeer inhoud delen
}
}
```
5. Veiligheid en betrouwbaarheid
- Beveilig de API met authenticatie en autorisatie
- Onderteken de JSON-configuratie om manipulatie te voorkomen
- Implementeer fallback opties (bijv. vorige bekende goede config)
- Overweeg een cache met vervaltijd om niet te vaak te hoeven ophalen
- Gebruik HTTPS voor dataverkeer
6. Best practices
- Test flags in staging voordat je ze in productie gebruikt
- Maak het mogelijk flags snel te kunnen wijzigen zonder app-update
- Log wijzigingen en gebruik statistieken om effect te monitoren
- Documenteer de flags en hun doeleinden
Door deze aanpak te volgen, heb je een flexibel, veilig en betrouwbaar feature flag systeem dat je in staat stelt je app dynamisch te beheren zonder telkens een nieuwe release te hoeven doen.
provider: deepseek
model: deepseek-chat
Hier is een complete handleiding voor het implementeren van een feature flag systeem in je Capacitor app:
## Architectuur Overzicht
Je feature flag systeem bestaat uit drie hoofdcomponenten:
1. **Configuratiebeheer** (remote configuratie)
2. **Lokale cache** (voor offline werking)
3. **Veilige communicatie** met je backend
## 1. Basis Feature Flag Service
```typescript
// src/services/FeatureFlagService.ts
export interface FeatureFlag {
name: string;
enabled: boolean;
description: string;
targetUsers?: string[]; // Voor user-based targeting
rolloutPercentage?: number; // Voor geleidelijke rollout
}
export class FeatureFlagService {
private flags: Map<string, FeatureFlag> = new Map();
private readonly CACHE_KEY = 'feature_flags_cache';
private readonly CACHE_DURATION = 5 * 60 * 1000; // 5 minuten
async initialize(): Promise<void> {
await this.loadCachedFlags();
await this.fetchRemoteFlags();
}
async fetchRemoteFlags(): Promise<void> {
try {
const response = await fetch('https://jouw-api.com/feature-flags', {
headers: {
'Authorization': `Bearer ${await this.getAuthToken()}`,
'Content-Type': 'application/json'
}
});
if (response.ok) {
const flags: FeatureFlag[] = await response.json();
this.updateFlags(flags);
await this.cacheFlags(flags);
}
} catch (error) {
console.warn('Failed to fetch remote flags, using cached version');
}
}
isEnabled(flagName: string, userId?: string): boolean {
const flag = this.flags.get(flagName);
if (!flag) {
return false; // Standaard uitgeschakeld als flag niet bestaat
}
if (!flag.enabled) {
return false;
}
// User-based targeting
if (userId && flag.targetUsers && flag.targetUsers.includes(userId)) {
return true;
}
// Percentage-based rollout
if (flag.rolloutPercentage && flag.rolloutPercentage < 100) {
return this.isUserInRollout(userId || 'anonymous', flag.rolloutPercentage);
}
return flag.enabled;
}
private isUserInRollout(userId: string, percentage: number): boolean {
// Creëer een consistente hash voor de gebruiker
const hash = this.stringToHash(userId);
return (hash % 100) < percentage;
}
private stringToHash(str: string): number {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = ((hash << 5) - hash) + str.charCodeAt(i);
hash |= 0; // Converteer naar 32-bit integer
}
return Math.abs(hash);
}
private async cacheFlags(flags: FeatureFlag[]): Promise<void> {
const cache = {
flags,
timestamp: Date.now()
};
await localStorage.setItem(this.CACHE_KEY, JSON.stringify(cache));
}
private async loadCachedFlags(): Promise<void> {
try {
const cached = await localStorage.getItem(this.CACHE_KEY);
if (cached) {
const { flags, timestamp } = JSON.parse(cached);
// Alleen gebruiken als cache niet verouderd is
if (Date.now() - timestamp < this.CACHE_DURATION) {
this.updateFlags(flags);
}
}
} catch (error) {
console.warn('Failed to load cached flags');
}
}
private updateFlags(flags: FeatureFlag[]): void {
this.flags.clear();
flags.forEach(flag => {
this.flags.set(flag.name, flag);
});
}
private async getAuthToken(): Promise<string> {
// Implementeer je authenticatie logica
return 'your-auth-token';
}
}
```
## 2. Feature Flag Definities
```typescript
// src/constants/FeatureFlags.ts
export const FEATURE_FLAGS = {
ENHANCED_AUTH: 'enhanced_auth',
SOCIAL_SHARING: 'social_sharing',
PREMIUM_CONTENT: 'premium_content',
DARK_MODE: 'dark_mode'
} as const;
```
## 3. Gebruik in je Componenten
```typescript
// src/components/LoginComponent.vue (of React/Angular)
import { FeatureFlagService } from '../services/FeatureFlagService';
import { FEATURE_FLAGS } from '../constants/FeatureFlags';
export default {
data() {
return {
useEnhancedAuth: false
};
},
async mounted() {
const featureService = new FeatureFlagService();
await featureService.initialize();
this.useEnhancedAuth = featureService.isEnabled(
FEATURE_FLAGS.ENHANCED_AUTH,
this.currentUserId
);
},
methods: {
async login() {
if (this.useEnhancedAuth) {
await this.enhancedLogin();
} else {
await this.basicLogin();
}
}
}
};
```
## 4. Backend API Voorbeeld (Node.js)
```javascript
// backend/routes/featureFlags.js
const express = require('express');
const router = express.Router();
// Feature flags configuratie
const featureFlags = {
enhanced_auth: {
name: 'enhanced_auth',
enabled: true,
description: 'Nieuwe authenticatie flow',
targetUsers: ['user123', 'user456'], // Specifieke gebruikers
rolloutPercentage: 50 // 50% van gebruikers
},
social_sharing: {
name: 'social_sharing',
enabled: false,
description: 'Social media sharing functionaliteit'
}
};
router.get('/', authenticateToken, (req, res) => {
// Filter flags op basis van gebruiker en rollout
const userFlags = Object.values(featureFlags).map(flag => ({
...flag,
enabled: shouldEnableForUser(flag, req.user.id)
}));
res.json(userFlags);
});
function shouldEnableForUser(flag, userId) {
if (!flag.enabled) return false;
if (flag.targetUsers && flag.targetUsers.includes(userId)) {
return true;
}
if (flag.rolloutPercentage) {
return isUserInRollout(userId, flag.rolloutPercentage);
}
return flag.enabled;
}
function isUserInRollout(userId, percentage) {
const hash = simpleHash(userId);
return (hash % 100) < percentage;
}
function simpleHash(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = ((hash << 5) - hash) + str.charCodeAt(i);
hash |= 0;
}
return Math.abs(hash);
}
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.sendStatus(401);
}
// JWT verificatie logica
jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, user) => {
if (err) return res.sendStatus(403);
req.user = user;
next();
});
}
module.exports = router;
```
## 5. Beveiligings Best Practices
```typescript
// src/services/SecureFeatureFlagService.ts
export class SecureFeatureFlagService extends FeatureFlagService {
private readonly encryptionKey = 'your-encryption-key';
async fetchRemoteFlags(): Promise<void> {
try {
const response = await fetch('https://jouw-api.com/feature-flags', {
headers: {
'Authorization': `Bearer ${await this.getAuthToken()}`,
'Content-Type': 'application/json',
'X-Device-ID': await this.getDeviceId()
}
});
if (response.ok) {
const encryptedData = await response.json();
const flags = await this.decryptFlags(encryptedData);
this.updateFlags(flags);
await this.cacheFlags(flags);
}
} catch (error) {
console.warn('Failed to fetch remote flags');
}
}
private async decryptFlags(encryptedData: string): Promise<FeatureFlag[]> {
// Implementeer decryptie logica
// Gebruik Web Crypto API voor veilige decryptie
return JSON.parse(encryptedData); // Vereenvoudigd voorbeeld
}
private async getDeviceId(): Promise<string> {
// Genereer een uniek device ID
const { Device } = await import('@capacitor/device');
const info = await Device.getId();
return info.uuid;
}
}
```
## 6. Monitoring en Logging
```typescript
// src/services/FeatureFlagAnalytics.ts
export class FeatureFlagAnalytics {
static async trackFlagUsage(flagName: string, enabled: boolean, userId: string): Promise<void> {
// Stuur usage data naar je analytics service
const event = {
event: 'feature_flag_evaluated',
flag_name: flagName,
enabled,
user_id: userId,
timestamp: new Date().toISOString()
};
await fetch('https://jouw-analytics.com/track', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(event)
});
}
}
```
## 7. App Initialisatie
```typescript
// src/main.ts
import { FeatureFlagService } from './services/FeatureFlagService';
const initializeApp = async () => {
// Initialiseer feature flags voor app start
const featureService = new FeatureFlagService();
await featureService.initialize();
// Maak service globaal beschikbaar (optioneel)
(window as any).featureFlags = featureService;
// Start je app
// ... app initialisatie code
};
```
## Best Practices voor Productie
1. **Fail Safe**: Feature flags moeten altijd een standaardwaarde hebben
2. **Caching**: Gebruik lokale cache voor offline beschikbaarheid
3. **Monitoring**: Track flag usage voor analytics
4. **Security**: Gebruik authenticatie en encryptie
5. **Performance**: Minimaliseer network calls met caching
6. **Documentatie**: Houd bij welke flags actief zijn en wat ze doen
7. **Cleanup**: Verwijder oude flags regelmatig
## Voorbeeld Gebruik voor Jouw Features
```typescript
// Gebruikersauthenticatie
const useBiometricAuth = featureService.isEnabled(
FEATURE_FLAGS.ENHANCED_AUTH,
currentUserId
);
// Inhoud delen
const canShareContent = featureService.isEnabled(
FEATURE_FLAGS.SOCIAL_SHARING,
currentUserId
);
```
Dit systeem geeft je de flexibiliteit om functies dynamisch in te schakelen zonder nieuwe app releases, terwijl het veilig en betrouwbaar blijft voor productiegebruik.

