HelloArmeria,ByeSpring
GihwanKim,LINE
KyeongsuYim,LINE
1
TableofContent
1.Why Armeriamigration?
2.Migrationsteps
Quick-win(Spring+ArmeriaClient)
Must-have(ArmeriaServer+TomcatService)
FinalGoal(ArmeriaOnly+SpringforDI)
Challenges
3.PerformanceHighlight-Streaminglargebodywithoutexhaustingresources
Spring'swayofuploading/downloadingabinary
Goingreactivewith Armeria!
2
WhyArmeriamigration?
ChannelGateway-APIGatewayserverforLINEfamilyservices&3rdpartyapps
Hasmanyupstreamservices
Serves8billion+requestsaday
Before:
SpringBoot+Tomcat(HTTP/1.1)
ApacheHttpClient
Thewholesystemgetseailyoverloadedevenonatinyupstreamfailure
Manyhackstoisolatetheissuestowheretheybelongto(e.g.dedicatedthreadpoolorservergroups)
3
WhyArmeriaMigration?
AfterArmeriamigration:
Asyncclient+microservicepatternimplementedviadecorators
Circuitbreaker,client-sideloadbalancingwithautomaticupstreamhealth
check
Upstreamfailurenolongerpropagatestotheotherservices..!
Servergetsmoreversatile
NativelyspeaksHTTP/2incleartext
Largebinarystreamingwithoutaffectingthelatencyofotherservices
4
Startingamigrationisabreeze..
..sinceArmeriaallowstokeepyourlegacywhilegettingthemostoutofit..!
5
Migration:Quick-win(SpringApp+ArmeriaClient)
JustaddArmeriacoredependencyanduseitspowerfulclienteverywhere!
@Bean

public WebClient lineAPIClient() {

return WebClient.builder("https://api.line.me")

.decorator(LoggingClient.newDecorator())

.decorator(ContentPreviewingClient.newDecorator(2048))

.decorator(RetryingClient.newDecorator(RetryRule.failsafe()))

.decorator(CircuitBreakerClient.newDecorator(..))

.build();

}

final AggregateHttpResponse res = lineAPIClient.get("/v2/profile").aggregate().join();
Microservicepatternwithoutintroducinganotherlayer
Failsaferetry,circuitbreaker,client-sideloadbalancing,metriccollectingandmuchmore..!
6
Migration:Must-have(ArmeriaServer+TomcatService)
ReplacethewholenetworklayerwithArmeria!
..byaddingjustfewlinesofcode.
@Bean

public ArmeriaServerConfigurator serverConfigurator(ServletWebServerApplicationContext context) {

TomcatWebServer container = (TomcatWebServer) context.getWebServer();

container.start();

return sb -> {

sb.serviceUnder("/", TomcatService.of(container.getTomcat()));

}

}

Youcanaddasync gRPC , Thrift servicestothesameportwithyourRESTservices
withoutTomcatoverhead
Yourservernativelyspeaks HTTP/2 bothonTLSandcleartext
...still,youkeepservingyour Tomcat legacyasisvia TomcatService
7
Migration:FinalGoal(ArmeriaOnly&SpringforDI)
Replace @Controller swithArmeria's AnnotatedService .
@RestController("/coffee")

class CoffeeController {

private final CoffeeService coffeeService;

public CoffeeController(CoffeeService coffeeService) {

this.coffeeService = coffeeService;

}

@PostMapping("/brew")
public void brewCoffee() {

coffeeService.brew();

}

}

8
Migration:FinalGoal(ArmeriaOnly&SpringforDI)
Replace @Controller swithArmeria's AnnotatedService .
@ConsumesJson

@ProducesJson

@PathPrefix("/coffee")

class CoffeeRouter {

private final CoffeeService coffeeService;

public CoffeeRouter(CoffeeService coffeeService) {

this.coffeeService = coffeeService;

}

@Post("/brew")

public CompletableFuture<Void> brewCoffee() {

coffeeService.brew();

}

}

9
Migration:FinalGoal(ArmeriaOnly&SpringforDI)
Register AnnotatedService toArmeriaserver.
@Bean

public ArmeriaServerConfigurator configureServer(CoffeeService coffeeService) {
return sb -> {

sb.annotatedService(new CoffeeRouter(coffeeService));

};

}

Nowyourserverrunsfullyasynchronous..!
Armeriahandlesthewhole request - response pipeline.
SupportsvariousJVMasynctechnologies.
kotlin-coroutine , RxJava , Reactor , CompletableFuture
but,youstillbenefitfromSpring's auto-configuration &DI..!
Allspringfeatures&integrationsrunjustfine (e.g.spring-starters).
10
Migration:Challenges
RunningblockingcodeinArmeriaapplication
// Your blocking code..

public void brew() {

coffeeJdbcRepository.save(new Coffee("cold brew"));

}

@Post("/brew")

@Blocking // Should always run in a blocking thread.

public void brewCoffee() {

coffeeService.brew();
}

Veryeasytoshootyourownfootbyblockingeventloops
Youshouldalwaysbecarefulofthethreadsthatyourlegacyblockingcoderunson
11
Migration:Challenges
Managing RequestContext hoppingthreads
public CompletableFuture<Void> brewAsync() {

return CompletableFuture

.supplyAsync(() -> coffeeJdbcRepository.save(new Coffee()))

.thenApply(() -> {

RequestContext.current(); // This will throw an error.

});

}

Areqeustishandledonvariousthreads
Oldwayofrelyingon ThreadLocal doesn'tworkanymore..
RequestContextHooks comestotherescue
12
PerformanceHighlight
WemigratedanAPIfordownloadingamessage'smediacontent(imageorvideo)
Withcustomizedauth,additionalHTTPcallduringdownload
NGINXwiththeOpenRestywasourfirstbet,butstoppedthemigrationsince:
AdditionalcostoflearningLua
DuplicatecodebetweenJavaandLua
DifficultyoftestingNGINX
13
PerformanceHighlight
Armeriacomestotherescue
UseexistingwebapplicationserverrunningonArmeria&Tomcat(SpringMVC)
OtherAPIsareservedonTomcat,butthecontentdownloadAPIisonArmeria
only
About25%performancegaininlatencycomparedtoSpringMVC
14
StreamingMadeEasywithArmeria
Spring'swayofservinglargepayload
@GetMapping("/download")

public ResponseEntity<byte[]> downloadImage() {

byte[] image = restTemplate.get("https://example.com");

return ResponseEntity.ok(image);

}

Whathappensif...:
therearemanyrequestsoraburst?
somerequeststaketoolongtimetoserve?
Threadpoolgetsexhaustedbecauseservletshouldallocateadedicatedthreadforarequest
OutOfMemoryException maybeobservedsincethewholecontentshouldbeloadedonJVM
heapbeforesendingaresponse
15
StreamingMadeEasywithArmeria
Armeriacanbeasilverbullet
Armeriaiscompletelyasynchronousandreactive
Wedon'tneedadedicatedthreadanymore
RelaysmallchunkofHTTPdataone-by-onetoclient/upstreamassoonasreceive
fromupstream/client
AllisdonewithoutcopyingcontentontheJVMheap
16
StreamingMadeEasywithArmeria
GoingReactive
17
StreamingMadeEasywithArmeria
Downloadandcustomizeresponseheaders
private WebClient upstreamClient = WebClient.of("https://example.com");

public HttpResponse serve(ServiceRequestContext ctx, HttpRequest req) {

// You can do anything (such as request validation)

HttpResponse res = upstreamClient.get("/a-very-big-file")

.mapHeaders(headers -> {

HttpHeaders customizedHeaders = ...
return customizedHeaders;

});

// You can do anything

return res;

}

Downloadafilefromhttps://example.com/a-very-big-fileandcustomizeHTTP
headers(e.g.sanitizing,additionalheaders) 18
StreamingMadeEasywithArmeria
Uploadingisalsojustaseasy
private WebClient upstreamClient = WebClient.of("https://example.com");

public HttpResponse serve(ServiceRequestContext ctx, HttpRequest req) {

// You can do anything (such as request validation)

HttpResponse res = upstreamClient.execute(req);

// You can do anything

return res;

}

RelaythegivenHTTPrequesttohttps://example.comregardlessofthesizeofbody
19
StreamingMadeEasywithArmeria
Intherealworld
20
Thankyou
21

Hello Armeria, Bye Spring