@croft#Devoxx #MicroProfile
Eclipse MicroProfile:
A Quest for a Lightweight and Modern Enterprise Java Platform
Mike Croft
Payara
@croft#Devoxx #MicroProfile
The Quest
• What is MicroProfile?
• Why MicroProfile?
• How do I get it?
• What APIs do I get?
• How do I use them?
@croft#Devoxx #MicroProfile
What is MicroProfile?
• A collection of APIs
• Focused on, but not limited to, microservices
• A place for innovation
• A fast, time-boxed release cycle
• Community driven
@croft#Devoxx #MicroProfile
Why MicroProfile?
• Get involved in the future of Enterprise Java
• Java EE and the JCP are perceived as slow
• For your app
• The tech landscape is changing, microservice or not. MicroProfile
aims to provide tools to make the best use of new tech
@croft#Devoxx #MicroProfile
How do I get it?
• Choose your own adventure
• Payara Micro
• TomEE
• WildFly Swarm
• Open Liberty
• KumuluzEE
• Hammock
@croft#Devoxx #MicroProfile
What APIs do I get?
• MicroProfile 1.2
• Config 1.1
• Health Check 1.0
• Fault Tolerance 1.0
• JWT Propagation 1.0
• Metrics 1.0
@croft#Devoxx #MicroProfile
What APIs will I get?
• MicroProfile Future
• Open Tracing 1.0
• Open API 1.0
• TypeSafe Rest Client 1.0
• JSON-B 1.0
• Java EE 8 upgrades
• Maintenance Releases
UNCONFIRMED!
#Devoxx #MicroProfile @croft
Config 1.1
@croft#Devoxx #MicroProfile
Config 1.1
• Inject config values at runtime
• NOT a registry
• Values are read-only by design
• Specify your own config sources
• Ordinals for priority
• Higher number == higher priority
@croft#Devoxx #MicroProfile
Config 1.1
• Default Config Sources:
• META-INF/microprofile-config.properties (100)
• Environment variables (300)
• System Properties (400)
• Implementor (Payara) Default Config Sources:
• domain
• config
• server
• application
• module
• cluster
• jndi
@croft#Devoxx #MicroProfile
Config 1.1
• @ConfigProperty
• name
• defaultValue
@Inject
@ConfigProperty(name = "example.some.url")
private String someUrl;
@Inject
@ConfigProperty(name = "example.some.port")
private Optional<Integer> somePort;
@Inject
@ConfigProperty(name = "example.timeout",
defaultValue = "100")
private javax.inject.Provider<Long> timeout;
@croft#Devoxx #MicroProfile
Config 1.1
• Converters
• boolean/Boolean
• int and Integer
• long and Long
• float and Float
• double and Double
• Duration
• LocalTime, LocalDate, LocalDateTime
• OffsetDateTime, OffsetTime
• Instant
• URL
@croft#Devoxx #MicroProfile
Config 1.1
• Custom Config Sources:
• java.util.ServiceLoader
• For a single source
• ConfigSourceProvider
• For multiple sources
e.g. many properties files in JARs
public interface ConfigSource {
String CONFIG_ORDINAL = "config_ordinal";
Map<String, String> getProperties();
default Set<String> getPropertyNames() {
return getProperties().keySet();
}
default int getOrdinal() {
String configOrdinal =
getValue(CONFIG_ORDINAL);
if (configOrdinal != null) {
try {
return Integer.parseInt(configOrdinal);
} catch (NumberFormatException ignored) {
}
}
return 100;
}
String getValue(String propertyName);
String getName();
}
#Devoxx #MicroProfile @croft
Health Check 1.0
@croft#Devoxx #MicroProfile
Health Check 1.0
• @Health
• UP or DOWN
• Extra data can be optionally specified
@croft#Devoxx #MicroProfile
Health Check 1.0
• @Health
• Class annotation
• Must implement HealthCheck
• Must override call()
@Health
public class CheckDiskspace implements
HealthCheck {
@Override
public HealthCheckResponse call() {
return
HealthCheckResponse.named("diskspace")
.withData("free", "780mb")
.up()
.build();
}
}
#Devoxx #MicroProfile @croft
Metrics 1.0
@croft#Devoxx #MicroProfile
Metrics 1.0
• Annotations
• @Counted
• @Gauge
• @Metered
• @Timed
• @Metric
@croft#Devoxx #MicroProfile
Metrics 1.0
• @Counted
• monotonic
• Targets:
• Constructor
• Method
• Type
@Counted
public void serviceA() {
// only current
// invocations counted
}
@Counted(monotonic = true)
public void serviceB() {
// every invocation
// counted
}
@croft#Devoxx #MicroProfile
Metrics 1.0
• @Gauge
• unit
•Targets:
• Method
• Produces:
• Sampled value,
e.g. CPU temperature
@Gauge(unit = MetricUnits.MEGABYTES)
public long getHeapSize() {
return heapSize;
}
@Gauge(unit = MetricUnits.NONE)
public long getValue() {
return value;
}
@croft#Devoxx #MicroProfile
Metrics 1.0
• @Metered
• Targets:
• Constructor
• Method
• Type
• Produces:
• Mean throughput
• 1, 5, 15 minute moving avg
@Metered
public class MeteredBean {
public void serviceA() { ... }
public void serviceB() { ... }
}
@Metered
public void run(){
// do work
}
@croft#Devoxx #MicroProfile
Metrics 1.0
• @Timed
• Targets:
• Constructor
• Method
• Type
• Produces:
• Duration statistics
• Throughput statistics
@Timed
public class TimedBean {
public void serviceA() { ... }
public void serviceB() { ... }
}
@Timed
public void run(){
// do work
}
@croft#Devoxx #MicroProfile
Metrics 1.0
• @Metric
• Targets:
• Field
• Method
• Parameter
@Produces
@Metric(name = "hitPercent")
@ApplicationScoped
protected Gauge<Double> getHitPercent() {
return new Gauge<Double>() {
@Override
public Double getValue() {
return hits / total;
}
};
}
@Inject
public void init(
@Metric(name = "inst") Counter inst) {
inst.inc();
}
@croft#Devoxx #MicroProfile
Metrics 1.0
• Annotation Fields
• name
• absolute
• displayName
• description
• unit
• tags
@Counted(monotonic = true,
name = "Service B",
absolute = true,
displayName = "My Service B",
description = "Total invokes of B",
unit = MetricUnits.NONE,
tags = "key=value")
public void serviceB() {
}
@croft#Devoxx #MicroProfile
Metrics 1.0
• 3 Metrics registries
• base
• application
• vendor
• JSON format
• Prometheus text
format
{
"application": {
"hitCount": 45
},
"base": {
"thread.count": 33,
"thread.max.count": 47
},
"vendor": {
"someMetric": 100
}
}
@croft#Devoxx #MicroProfile
Metrics 1.0
• Sample prometheus.yml
• Found in the Vote service of the
Microprofile-Conference
application
• Liberty’s implementation is
secured by default, hence the need
for authentication
scrape_configs:
- job_name: 'microprofile-conference-vote'
tls_config:
insecure_skip_verify: true
basic_auth:
username: confAdmin
password: microprofile
scheme: https
static_configs:
- targets: ['localhost:9443']
#Devoxx #MicroProfile @croft
Fault Tolerance 1.0
@croft#Devoxx #MicroProfile
Fault Tolerance 1.0
• Implements common design patterns
• All annotations can be used together
• @Asynchronous
• @Retry
• @Fallback
• @CircuitBreaker
• @Bulkhead
• @Timeout
• See the spec for details
@croft#Devoxx #MicroProfile
Fault Tolerance 1.0
• @Asynchronous
• Annotate method or class
• Method(s) invoked in separate thread
• MUST return a Future
• Assumes requests should be synchronous if not specified
@croft#Devoxx #MicroProfile
Fault Tolerance 1.0
• @Retry
• delay (ms)
• delayUnit
• maxDuration (ms)
• durationUnit
• jitter (ms)
• jitterDelayUnit
• maxRetries
• retryOn
• abortOn
@Retry(delay = 400, maxDuration = 3200,
jitter = 400, maxRetries = 10)
public Connection serviceA() {
return connectionService();
}
@Retry(maxRetries = 90, maxDuration = 5,
durationUnit = ChronoUnit.SECONDS)
public void serviceB() {
writingService();
}
@Retry(retryOn = {IOException.class})
public void serviceC() {
readingService();
}
@croft#Devoxx #MicroProfile
Fault Tolerance 1.0
• @Fallback
• myFallback.class
• fallbackMethod
@Retry(maxRetries = 1)
@Fallback(StringFallbackHandler.class)
public String serviceZ() {
return "service A";
}
@Dependent
class StringFallbackHandler implements
FallbackHandler<String> {
@Override
public String handle(ExecutionContext
context){
return "Fallback for service A";
}
}
@Retry(maxRetries = 2)
@Fallback(fallbackMethod =
"fallbackForServiceB")
public String serviceB() {
return nameService();
}
private String fallbackForServiceB() {
return "myFallback";
}
@croft#Devoxx #MicroProfile
Fault Tolerance 1.0
• @CircuitBreaker
• successThreshold
• requestVolumeThreshold
• failureRatio
• delay
• Throws:
• CircuitBreakerOpenException
@CircuitBreaker(
successThreshold = 10,
requestVolumeThreshold = 4,
failureRatio=0.75,
delay = 1000)
public Connection serviceA() {
return connectionService();
}
@croft#Devoxx #MicroProfile
Fault Tolerance 1.0
•@Bulkhead
• Semaphore mode:
• value
• Threadpool mode:
• value
• waitingTaskQueue
• with @Asynchronous
• Throws:
• BulkheadException
In ThreadPool mode
@Bulkhead(5)
public Connection serviceA1() {
return connectionService();
}
@Asynchronous
@Bulkhead(value = 5,
waitingTaskQueue = 8)
public Future<Connection> serviceA2() {
return CompletableFuture.completedFuture(
connectionService());
}
@croft#Devoxx #MicroProfile
Fault Tolerance 1.0
• @Timeout
• Timeout in ms
• Can trigger @Fallback
• Can trigger @Retry
• Contributes to opening a Circuit
• Throws:
• TimeoutException
@Timeout(400)
public Connection serviceA() {
return connectionService();
}
#Devoxx #MicroProfile @croft
JWT Propagation 1.0
@croft#Devoxx #MicroProfile
JWT Propagation 1.0
• Token-based authentication
• Easily validated without second round-trip
• JSON format data
• Uses claims to carry auth information about a subject
• Processing trivial with JSON-P
• Agreed set of standard claims for interoperability
@croft#Devoxx #MicroProfile
JWT Propagation 1.0
• MicroProfile JWT Tokens can...
• Be used for authentication
• Be used for authorization
• Be mapped to JSR375 IdentityStore
• Support additional IANA standard claims
• Support additional non-standard claims
• 2 new standard claims
• upn: user principal
• groups: token subject’s group memberships
@croft#Devoxx #MicroProfile
JWT Propagation 1.0
• Min. Required Claims
• typ
• alg
• kid
• iss
• sub
• exp
• iat
• jti
• upn
• groups
{
"typ": "JWT",
"alg": "RS256",
"kid": "abc-1234567890"
}
{
"iss": "https://server.example.com",
"jti": "a-123",
"exp": 1311281970,
"iat": 1311280970,
"sub": "24400320",
"upn": "jdoe@server.example.com",
"groups": ["red-group", "green-group",
"admin-group", "admin"
]
}
@croft#Devoxx #MicroProfile
JWT Propagation 1.0
• @Claim
• Claims enum available for
standard claims
• Ambiguous uses throw
DeploymentException
• For complete details,
see the specification doc
@ApplicationScoped
public class MyEndpoint {
@Inject
@Claim(value = "exp",
standard = Claims.iat)
private Long timeClaim;
// Mixing exp with iat
// Throws exception
@Claim(standard = Claims.iss)
private Long timeClaim;
}
@croft#Devoxx #MicroProfile
Find Out More...
As a user...
• Choose your implementation!
• What specs does it support?
• Check the release notes…
• For the microprofile-bom
• For the individual specs
• Ask questions!
• In Gitter!
• In the Google Group!
@croft#Devoxx #MicroProfile
Find Out More...
As a contributor...
• What specifications are you interested in?
• An existing spec?
• Find the repository on GitHub (or on microprofile.io)
• Find the chat community on Gitter
• Find the hangout in the MicroProfile Calendar
• Something new?
• Start a discussion in the Google Group!
• Start coding in the Sandbox repository
• (PRs should be merged quickly)
Demo
@croft#Devoxx #MicroProfile
@croft#Devoxx #MicroProfile
Questions?
• How does MicroProfile relate to EE4J?
• How big is MicroProfile planning to get?
• There are no implementation rules - could be composable
• There will be some interdepency though - e.g. Config API widely
reused
• What about breaking changes?
• MicroProfile is about innovation primarily. Breaking changes will be
avoided where possible, but not if it limits progress.

MicroProfile: A Quest for a Lightweight and Modern Enterprise Java Platform

  • 1.
    @croft#Devoxx #MicroProfile Eclipse MicroProfile: AQuest for a Lightweight and Modern Enterprise Java Platform Mike Croft Payara
  • 2.
    @croft#Devoxx #MicroProfile The Quest •What is MicroProfile? • Why MicroProfile? • How do I get it? • What APIs do I get? • How do I use them?
  • 3.
    @croft#Devoxx #MicroProfile What isMicroProfile? • A collection of APIs • Focused on, but not limited to, microservices • A place for innovation • A fast, time-boxed release cycle • Community driven
  • 4.
    @croft#Devoxx #MicroProfile Why MicroProfile? •Get involved in the future of Enterprise Java • Java EE and the JCP are perceived as slow • For your app • The tech landscape is changing, microservice or not. MicroProfile aims to provide tools to make the best use of new tech
  • 5.
    @croft#Devoxx #MicroProfile How doI get it? • Choose your own adventure • Payara Micro • TomEE • WildFly Swarm • Open Liberty • KumuluzEE • Hammock
  • 6.
    @croft#Devoxx #MicroProfile What APIsdo I get? • MicroProfile 1.2 • Config 1.1 • Health Check 1.0 • Fault Tolerance 1.0 • JWT Propagation 1.0 • Metrics 1.0
  • 7.
    @croft#Devoxx #MicroProfile What APIswill I get? • MicroProfile Future • Open Tracing 1.0 • Open API 1.0 • TypeSafe Rest Client 1.0 • JSON-B 1.0 • Java EE 8 upgrades • Maintenance Releases UNCONFIRMED!
  • 8.
  • 9.
    @croft#Devoxx #MicroProfile Config 1.1 •Inject config values at runtime • NOT a registry • Values are read-only by design • Specify your own config sources • Ordinals for priority • Higher number == higher priority
  • 10.
    @croft#Devoxx #MicroProfile Config 1.1 •Default Config Sources: • META-INF/microprofile-config.properties (100) • Environment variables (300) • System Properties (400) • Implementor (Payara) Default Config Sources: • domain • config • server • application • module • cluster • jndi
  • 11.
    @croft#Devoxx #MicroProfile Config 1.1 •@ConfigProperty • name • defaultValue @Inject @ConfigProperty(name = "example.some.url") private String someUrl; @Inject @ConfigProperty(name = "example.some.port") private Optional<Integer> somePort; @Inject @ConfigProperty(name = "example.timeout", defaultValue = "100") private javax.inject.Provider<Long> timeout;
  • 12.
    @croft#Devoxx #MicroProfile Config 1.1 •Converters • boolean/Boolean • int and Integer • long and Long • float and Float • double and Double • Duration • LocalTime, LocalDate, LocalDateTime • OffsetDateTime, OffsetTime • Instant • URL
  • 13.
    @croft#Devoxx #MicroProfile Config 1.1 •Custom Config Sources: • java.util.ServiceLoader • For a single source • ConfigSourceProvider • For multiple sources e.g. many properties files in JARs public interface ConfigSource { String CONFIG_ORDINAL = "config_ordinal"; Map<String, String> getProperties(); default Set<String> getPropertyNames() { return getProperties().keySet(); } default int getOrdinal() { String configOrdinal = getValue(CONFIG_ORDINAL); if (configOrdinal != null) { try { return Integer.parseInt(configOrdinal); } catch (NumberFormatException ignored) { } } return 100; } String getValue(String propertyName); String getName(); }
  • 14.
  • 15.
    @croft#Devoxx #MicroProfile Health Check1.0 • @Health • UP or DOWN • Extra data can be optionally specified
  • 16.
    @croft#Devoxx #MicroProfile Health Check1.0 • @Health • Class annotation • Must implement HealthCheck • Must override call() @Health public class CheckDiskspace implements HealthCheck { @Override public HealthCheckResponse call() { return HealthCheckResponse.named("diskspace") .withData("free", "780mb") .up() .build(); } }
  • 17.
  • 18.
    @croft#Devoxx #MicroProfile Metrics 1.0 •Annotations • @Counted • @Gauge • @Metered • @Timed • @Metric
  • 19.
    @croft#Devoxx #MicroProfile Metrics 1.0 •@Counted • monotonic • Targets: • Constructor • Method • Type @Counted public void serviceA() { // only current // invocations counted } @Counted(monotonic = true) public void serviceB() { // every invocation // counted }
  • 20.
    @croft#Devoxx #MicroProfile Metrics 1.0 •@Gauge • unit •Targets: • Method • Produces: • Sampled value, e.g. CPU temperature @Gauge(unit = MetricUnits.MEGABYTES) public long getHeapSize() { return heapSize; } @Gauge(unit = MetricUnits.NONE) public long getValue() { return value; }
  • 21.
    @croft#Devoxx #MicroProfile Metrics 1.0 •@Metered • Targets: • Constructor • Method • Type • Produces: • Mean throughput • 1, 5, 15 minute moving avg @Metered public class MeteredBean { public void serviceA() { ... } public void serviceB() { ... } } @Metered public void run(){ // do work }
  • 22.
    @croft#Devoxx #MicroProfile Metrics 1.0 •@Timed • Targets: • Constructor • Method • Type • Produces: • Duration statistics • Throughput statistics @Timed public class TimedBean { public void serviceA() { ... } public void serviceB() { ... } } @Timed public void run(){ // do work }
  • 23.
    @croft#Devoxx #MicroProfile Metrics 1.0 •@Metric • Targets: • Field • Method • Parameter @Produces @Metric(name = "hitPercent") @ApplicationScoped protected Gauge<Double> getHitPercent() { return new Gauge<Double>() { @Override public Double getValue() { return hits / total; } }; } @Inject public void init( @Metric(name = "inst") Counter inst) { inst.inc(); }
  • 24.
    @croft#Devoxx #MicroProfile Metrics 1.0 •Annotation Fields • name • absolute • displayName • description • unit • tags @Counted(monotonic = true, name = "Service B", absolute = true, displayName = "My Service B", description = "Total invokes of B", unit = MetricUnits.NONE, tags = "key=value") public void serviceB() { }
  • 25.
    @croft#Devoxx #MicroProfile Metrics 1.0 •3 Metrics registries • base • application • vendor • JSON format • Prometheus text format { "application": { "hitCount": 45 }, "base": { "thread.count": 33, "thread.max.count": 47 }, "vendor": { "someMetric": 100 } }
  • 26.
    @croft#Devoxx #MicroProfile Metrics 1.0 •Sample prometheus.yml • Found in the Vote service of the Microprofile-Conference application • Liberty’s implementation is secured by default, hence the need for authentication scrape_configs: - job_name: 'microprofile-conference-vote' tls_config: insecure_skip_verify: true basic_auth: username: confAdmin password: microprofile scheme: https static_configs: - targets: ['localhost:9443']
  • 27.
  • 28.
    @croft#Devoxx #MicroProfile Fault Tolerance1.0 • Implements common design patterns • All annotations can be used together • @Asynchronous • @Retry • @Fallback • @CircuitBreaker • @Bulkhead • @Timeout • See the spec for details
  • 29.
    @croft#Devoxx #MicroProfile Fault Tolerance1.0 • @Asynchronous • Annotate method or class • Method(s) invoked in separate thread • MUST return a Future • Assumes requests should be synchronous if not specified
  • 30.
    @croft#Devoxx #MicroProfile Fault Tolerance1.0 • @Retry • delay (ms) • delayUnit • maxDuration (ms) • durationUnit • jitter (ms) • jitterDelayUnit • maxRetries • retryOn • abortOn @Retry(delay = 400, maxDuration = 3200, jitter = 400, maxRetries = 10) public Connection serviceA() { return connectionService(); } @Retry(maxRetries = 90, maxDuration = 5, durationUnit = ChronoUnit.SECONDS) public void serviceB() { writingService(); } @Retry(retryOn = {IOException.class}) public void serviceC() { readingService(); }
  • 31.
    @croft#Devoxx #MicroProfile Fault Tolerance1.0 • @Fallback • myFallback.class • fallbackMethod @Retry(maxRetries = 1) @Fallback(StringFallbackHandler.class) public String serviceZ() { return "service A"; } @Dependent class StringFallbackHandler implements FallbackHandler<String> { @Override public String handle(ExecutionContext context){ return "Fallback for service A"; } } @Retry(maxRetries = 2) @Fallback(fallbackMethod = "fallbackForServiceB") public String serviceB() { return nameService(); } private String fallbackForServiceB() { return "myFallback"; }
  • 32.
    @croft#Devoxx #MicroProfile Fault Tolerance1.0 • @CircuitBreaker • successThreshold • requestVolumeThreshold • failureRatio • delay • Throws: • CircuitBreakerOpenException @CircuitBreaker( successThreshold = 10, requestVolumeThreshold = 4, failureRatio=0.75, delay = 1000) public Connection serviceA() { return connectionService(); }
  • 33.
    @croft#Devoxx #MicroProfile Fault Tolerance1.0 •@Bulkhead • Semaphore mode: • value • Threadpool mode: • value • waitingTaskQueue • with @Asynchronous • Throws: • BulkheadException In ThreadPool mode @Bulkhead(5) public Connection serviceA1() { return connectionService(); } @Asynchronous @Bulkhead(value = 5, waitingTaskQueue = 8) public Future<Connection> serviceA2() { return CompletableFuture.completedFuture( connectionService()); }
  • 34.
    @croft#Devoxx #MicroProfile Fault Tolerance1.0 • @Timeout • Timeout in ms • Can trigger @Fallback • Can trigger @Retry • Contributes to opening a Circuit • Throws: • TimeoutException @Timeout(400) public Connection serviceA() { return connectionService(); }
  • 35.
  • 36.
    @croft#Devoxx #MicroProfile JWT Propagation1.0 • Token-based authentication • Easily validated without second round-trip • JSON format data • Uses claims to carry auth information about a subject • Processing trivial with JSON-P • Agreed set of standard claims for interoperability
  • 37.
    @croft#Devoxx #MicroProfile JWT Propagation1.0 • MicroProfile JWT Tokens can... • Be used for authentication • Be used for authorization • Be mapped to JSR375 IdentityStore • Support additional IANA standard claims • Support additional non-standard claims • 2 new standard claims • upn: user principal • groups: token subject’s group memberships
  • 38.
    @croft#Devoxx #MicroProfile JWT Propagation1.0 • Min. Required Claims • typ • alg • kid • iss • sub • exp • iat • jti • upn • groups { "typ": "JWT", "alg": "RS256", "kid": "abc-1234567890" } { "iss": "https://server.example.com", "jti": "a-123", "exp": 1311281970, "iat": 1311280970, "sub": "24400320", "upn": "jdoe@server.example.com", "groups": ["red-group", "green-group", "admin-group", "admin" ] }
  • 39.
    @croft#Devoxx #MicroProfile JWT Propagation1.0 • @Claim • Claims enum available for standard claims • Ambiguous uses throw DeploymentException • For complete details, see the specification doc @ApplicationScoped public class MyEndpoint { @Inject @Claim(value = "exp", standard = Claims.iat) private Long timeClaim; // Mixing exp with iat // Throws exception @Claim(standard = Claims.iss) private Long timeClaim; }
  • 40.
    @croft#Devoxx #MicroProfile Find OutMore... As a user... • Choose your implementation! • What specs does it support? • Check the release notes… • For the microprofile-bom • For the individual specs • Ask questions! • In Gitter! • In the Google Group!
  • 41.
    @croft#Devoxx #MicroProfile Find OutMore... As a contributor... • What specifications are you interested in? • An existing spec? • Find the repository on GitHub (or on microprofile.io) • Find the chat community on Gitter • Find the hangout in the MicroProfile Calendar • Something new? • Start a discussion in the Google Group! • Start coding in the Sandbox repository • (PRs should be merged quickly)
  • 42.
  • 43.
    @croft#Devoxx #MicroProfile Questions? • Howdoes MicroProfile relate to EE4J? • How big is MicroProfile planning to get? • There are no implementation rules - could be composable • There will be some interdepency though - e.g. Config API widely reused • What about breaking changes? • MicroProfile is about innovation primarily. Breaking changes will be avoided where possible, but not if it limits progress.