datetime based feature flags
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
package io.reflectoring.featureflags;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface FeatureFlagService {
|
||||
|
||||
/**
|
||||
@@ -24,4 +27,21 @@ public interface FeatureFlagService {
|
||||
|
||||
Boolean isNewServiceEnabled();
|
||||
|
||||
/**
|
||||
* Returns the current time to be used by the welcome message feature.
|
||||
*/
|
||||
Optional<LocalDateTime> currentDateForWelcomeMessage();
|
||||
|
||||
/**
|
||||
* Returns the current time to be used by the welcome email feature.
|
||||
*/
|
||||
Optional<LocalDateTime> currentDateForWelcomeEmails();
|
||||
|
||||
Optional<LocalDateTime> currentDateForWelcomeMessage(String username);
|
||||
|
||||
/**
|
||||
* Returns the current time to be used by the welcome email feature.
|
||||
*/
|
||||
Optional<LocalDateTime> currentDateForWelcomeEmails(String username);
|
||||
|
||||
}
|
||||
|
||||
@@ -2,8 +2,10 @@ package io.reflectoring.featureflags;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableScheduling
|
||||
public class FeatureFlagsApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package io.reflectoring.featureflags.datebased;
|
||||
|
||||
import io.reflectoring.featureflags.FeatureFlagService;
|
||||
import io.reflectoring.featureflags.web.UserSession;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
@Controller
|
||||
public class DateFeatureFlagController {
|
||||
|
||||
private final UserSession userSession;
|
||||
private final FeatureFlagService featureFlagService;
|
||||
|
||||
DateFeatureFlagController(UserSession userSession, FeatureFlagService featureFlagService) {
|
||||
this.userSession = userSession;
|
||||
this.featureFlagService = featureFlagService;
|
||||
}
|
||||
|
||||
@GetMapping(path = {"/welcome"})
|
||||
ModelAndView welcome() {
|
||||
userSession.login("ben");
|
||||
|
||||
Optional<LocalDateTime> date = featureFlagService.currentDateForWelcomeMessage();
|
||||
|
||||
if (date.isEmpty()) {
|
||||
return new ModelAndView("/welcome-page-without-message.html");
|
||||
}
|
||||
|
||||
LocalTime time = date.get().toLocalTime();
|
||||
String welcomeMessage = "";
|
||||
|
||||
if (time.isBefore(LocalTime.NOON)) {
|
||||
welcomeMessage = "Good Morning!";
|
||||
} else if (time.isBefore(LocalTime.of(17, 0))) {
|
||||
welcomeMessage = "Good Day!";
|
||||
} else {
|
||||
welcomeMessage = "Good Evening!";
|
||||
}
|
||||
|
||||
return new ModelAndView("/welcome-page.html", Map.of("welcomeMessage", welcomeMessage));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package io.reflectoring.featureflags.datebased;
|
||||
|
||||
import io.reflectoring.featureflags.FeatureFlagService;
|
||||
import io.reflectoring.featureflags.web.UserSession;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Component
|
||||
public class EmailSender {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(EmailSender.class);
|
||||
private final FeatureFlagService featureFlagService;
|
||||
|
||||
public EmailSender(FeatureFlagService featureFlagService, UserSession userSession) {
|
||||
this.featureFlagService = featureFlagService;
|
||||
}
|
||||
|
||||
@Scheduled(fixedDelay = 10000)
|
||||
public void sendWelcomeEmails() {
|
||||
for (User user : getUsers()) {
|
||||
Optional<LocalDateTime> now = featureFlagService.currentDateForWelcomeEmails(user.name);
|
||||
if (now.isEmpty()) {
|
||||
logger.info("not sending email to user {}", user.name);
|
||||
continue;
|
||||
}
|
||||
if (user.registrationDate.isBefore(now.get().minusDays(14L).toLocalDate())) {
|
||||
sendEmail(user, "Welcome email after 14 days");
|
||||
} else if (user.registrationDate.isBefore(now.get().minusDays(7L).toLocalDate())) {
|
||||
sendEmail(user, "Welcome email after 7 days");
|
||||
} else if (user.registrationDate.isBefore(now.get().minusDays(1L).toLocalDate())) {
|
||||
sendEmail(user, "Welcome email after 1 day");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sendEmail(User user, String message) {
|
||||
logger.info("sending email to user {} with message {}", user.name, message);
|
||||
}
|
||||
|
||||
private List<User> getUsers() {
|
||||
return Arrays.asList(
|
||||
new User("alice", LocalDate.now().minusDays(1)),
|
||||
new User("bob", LocalDate.now().minusDays(2)),
|
||||
new User("charlie", LocalDate.now().minusDays(3)),
|
||||
new User("ben", LocalDate.now().minusDays(4)),
|
||||
new User("tom", LocalDate.now().minusDays(5)),
|
||||
new User("hugo", LocalDate.now().minusDays(6))
|
||||
);
|
||||
}
|
||||
|
||||
static class User {
|
||||
|
||||
private final String name;
|
||||
private final LocalDate registrationDate;
|
||||
|
||||
User(String name, LocalDate registrationDate) {
|
||||
this.name = name;
|
||||
this.registrationDate = registrationDate;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,6 +4,9 @@ import io.reflectoring.featureflags.FeatureFlagService;
|
||||
import org.ff4j.FF4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Optional;
|
||||
|
||||
@Component("ff4j")
|
||||
public class FF4JFeatureFlagService implements FeatureFlagService {
|
||||
|
||||
@@ -38,4 +41,28 @@ public class FF4JFeatureFlagService implements FeatureFlagService {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<LocalDateTime> currentDateForWelcomeMessage() {
|
||||
// not supported
|
||||
return Optional.of(LocalDateTime.now());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<LocalDateTime> currentDateForWelcomeEmails() {
|
||||
// not supported
|
||||
return Optional.of(LocalDateTime.now());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<LocalDateTime> currentDateForWelcomeMessage(String username) {
|
||||
// not supported
|
||||
return Optional.of(LocalDateTime.now());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<LocalDateTime> currentDateForWelcomeEmails(String username) {
|
||||
// not supported
|
||||
return Optional.of(LocalDateTime.now());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,17 +4,25 @@ import com.launchdarkly.sdk.LDUser;
|
||||
import com.launchdarkly.sdk.server.LDClient;
|
||||
import io.reflectoring.featureflags.FeatureFlagService;
|
||||
import io.reflectoring.featureflags.web.UserSession;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.Optional;
|
||||
|
||||
@Component("launchdarkly")
|
||||
@Primary
|
||||
public class LaunchDarklyFeatureFlagService implements FeatureFlagService {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(LaunchDarklyFeatureFlagService.class);
|
||||
private final LDClient launchdarklyClient;
|
||||
private final UserSession userSession;
|
||||
private final DateTimeFormatter dateFormatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
|
||||
|
||||
public LaunchDarklyFeatureFlagService(LDClient launchdarklyClient, UserSession userSession) {
|
||||
this.launchdarklyClient = launchdarklyClient;
|
||||
@@ -63,9 +71,91 @@ public class LaunchDarklyFeatureFlagService implements FeatureFlagService {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<LocalDateTime> currentDateForWelcomeMessage() {
|
||||
String stringValue = launchdarklyClient.stringVariation("current-date-for-welcome-message", getLaunchdarklyUserFromSession(), "false");
|
||||
|
||||
if ("false".equals(stringValue)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
if ("now".equals(stringValue)) {
|
||||
return Optional.of(LocalDateTime.now());
|
||||
}
|
||||
|
||||
try {
|
||||
return Optional.of(LocalDateTime.parse(stringValue, dateFormatter));
|
||||
} catch (DateTimeParseException e) {
|
||||
logger.warn("could not parse date ... falling back to current date", e);
|
||||
return Optional.of(LocalDateTime.now());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<LocalDateTime> currentDateForWelcomeEmails() {
|
||||
String stringValue = launchdarklyClient.stringVariation("current-date-for-welcome-email", getLaunchdarklyUserFromSession(), "false");
|
||||
|
||||
if ("false".equals(stringValue)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
if ("now".equals(stringValue)) {
|
||||
return Optional.of(LocalDateTime.now());
|
||||
}
|
||||
|
||||
try {
|
||||
return Optional.of(LocalDateTime.parse(stringValue, dateFormatter));
|
||||
} catch (DateTimeParseException e) {
|
||||
logger.warn("could not parse date ... falling back to current date", e);
|
||||
return Optional.of(LocalDateTime.now());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<LocalDateTime> currentDateForWelcomeMessage(String username) {
|
||||
String stringValue = launchdarklyClient.stringVariation("current-date-for-welcome-message", new LDUser.Builder(username).build(), "false");
|
||||
|
||||
if ("false".equals(stringValue)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
if ("now".equals(stringValue)) {
|
||||
return Optional.of(LocalDateTime.now());
|
||||
}
|
||||
|
||||
try {
|
||||
return Optional.of(LocalDateTime.parse(stringValue, dateFormatter));
|
||||
} catch (DateTimeParseException e) {
|
||||
logger.warn("could not parse date ... falling back to current date", e);
|
||||
return Optional.of(LocalDateTime.now());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<LocalDateTime> currentDateForWelcomeEmails(String username) {
|
||||
String stringValue = launchdarklyClient.stringVariation("current-date-for-welcome-email", new LDUser.Builder(username).build(), "false");
|
||||
|
||||
if ("false".equals(stringValue)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
if ("now".equals(stringValue)) {
|
||||
return Optional.of(LocalDateTime.now());
|
||||
}
|
||||
|
||||
try {
|
||||
return Optional.of(LocalDateTime.parse(stringValue, dateFormatter));
|
||||
} catch (DateTimeParseException e) {
|
||||
logger.warn("could not parse date ... falling back to current date", e);
|
||||
return Optional.of(LocalDateTime.now());
|
||||
}
|
||||
}
|
||||
|
||||
private LDUser getLaunchdarklyUserFromSession() {
|
||||
return new LDUser.Builder(userSession.getUsername())
|
||||
.custom("clicked", userSession.hasClicked())
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@ package io.reflectoring.featureflags.togglz;
|
||||
import io.reflectoring.featureflags.FeatureFlagService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Optional;
|
||||
|
||||
@Component("togglz")
|
||||
public class TooglzFeatureFlagService implements FeatureFlagService {
|
||||
|
||||
@@ -31,4 +34,29 @@ public class TooglzFeatureFlagService implements FeatureFlagService {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<LocalDateTime> currentDateForWelcomeMessage() {
|
||||
// not supported
|
||||
return Optional.of(LocalDateTime.now());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<LocalDateTime> currentDateForWelcomeEmails() {
|
||||
// not supported
|
||||
return Optional.of(LocalDateTime.now());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<LocalDateTime> currentDateForWelcomeMessage(String username) {
|
||||
// not supported
|
||||
return Optional.of(LocalDateTime.now());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<LocalDateTime> currentDateForWelcomeEmails(String username) {
|
||||
// not supported
|
||||
return Optional.of(LocalDateTime.now());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
<title>Welcome</title>
|
||||
<style>
|
||||
td {
|
||||
padding:10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
This is the version of the welcome page without a welcome message.
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
<title>Your features</title>
|
||||
<style>
|
||||
td {
|
||||
padding:10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<h1 th:text="${welcomeMessage}"></h1>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user