KodGemisi
Software development, consultancy and training
Kod Gemisi, Jun 2017 destan@kodgemisi.com
Java (Spring Framework, JavaEE)
Javascript (React, Angular)
Consultant
How many of you used...
The Ultimate solution?
Image source: https://martinfowler.com/articles/microservices.html
Microservices don't solve complexity
just move it somewhere else
Not just does this just move complexity around, it moves it to a place that's less explicit and harder to control.
M. Fowler
The
The
provides tools for developers to quickly build some of the common patterns in distributed systems
Spring Cloud won't solve your organizational problems!
Personal Money Tracker
Image source: https://github.com/sqshq/PiggyMetrics
Head first implementation!
@Repository
public interface AccountRepository extends CrudRepository<Account, String> {
Account findByName(String name);
}
@RestController
public class AccountController {
@Autowired
private AccountService accountService;
@RequestMapping(path = "/{name}", method = RequestMethod.GET)
public Account getAccountByName(@PathVariable String name) {
return accountService.findByName(name);
}
@RequestMapping(path = "/", method = RequestMethod.POST)
public Account createNewAccount(@Valid @RequestBody User user) {
return accountService.create(user);
}
//...
}
Account service up & running
Notification service up & running
via dynamic provisioning
Image source: https://www.nginx.com/blog/service-discovery-in-a-microservices-architecture
Image source: https://www.nginx.com/blog/service-discovery-in-a-microservices-architecture
Image source: https://github.com/Netflix/eureka/wiki/Eureka-at-a-glance#high-level-architecture
@SpringBootApplication
@EnableEurekaServer
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
server:
port: ${PORT:8761}
eureka:
client:
registerWithEureka: false
fetchRegistry: false
server:
waitTimeInMsWhenSyncEmpty: 0
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
@EnableEurekaServer
@SpringBootApplication
@EnableDiscoveryClient
@Configuration
public class AccountApplication {
//...
}
@EnableDiscoveryClient
eureka:
instance:
prefer-ip-address: true
client:
serviceUrl:
defaultZone: http://registry:8761/eureka/
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
Keeping track of dynamic instances is done.
What about selecting one?
Client Side Load Balancing
Feign Client + Ribbon
package com.piggymetrics.notification.client;
// imports...
@FeignClient(name = "account-service")
public interface AccountServiceClient {
@RequestMapping(method = RequestMethod.GET,
value = "/accounts/{accountName}",
consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
String getAccount(@PathVariable("accountName") String accountName);
}
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
@Service
public class NotificationServiceImpl implements NotificationService {
@Autowired
private AccountServiceClient client;
@Override
public void sendBackupNotifications() {
//...
recipients.forEach(recipient -> CompletableFuture.runAsync(() -> {
try {
String attachment = client.getAccount(recipient.getAccountName());
//...
} catch (Throwable t) {
log.error("an error during backup notification for {}", recipient, t);
}
}));
}
}
@RestController
public class SampleController {
@Autowired
private RestTemplate restTemplate;
@Autowired
@LoadBalanced
private RestTemplate loadBalanced;
@RequestMapping("/sample-product")
public Product hi() {
Product sampleProduct =
loadBalanced.getForObject("http://izmir-store/sample", Product.class);
return sampleProduct;
}
}
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
@SpringBootApplication
@EnableConfigServer
public class ConfigApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigApplication.class, args);
}
}
spring.cloud.config.server.git.uri=${HOME}/Desktop/config
spring.cloud.config.server.native.search-locations: classpath:/shared
OR
spring:
cloud:
config:
server:
native:
search-locations: classpath:/shared
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
@RefreshScope
@RestController
class MessageRestController {
@Value("${message:Hello default}")
private String message;
@RequestMapping("/message")
String getMessage() {
return this.message;
}
}
http://localhost:8080/refresh
Image source: https://martinfowler.com/bliki/CircuitBreaker.html
@SpringBootApplication
@EnableCircuitBreaker
public class Application {
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class).web(true).run(args);
}
}
@Component
public class StoreIntegration {
@HystrixCommand(fallbackMethod = "defaultStores")
public Object getStores(Map<String, Object> parameters) {
//do stuff that might fail
}
public Object defaultStores(Map<String, Object> parameters) {
return /* something useful */;
}
}
spring-cloud-starter-hystrix-dashboard
+
@EnableHystrixDashboard
@RestController
public class SampleController {
@Autowired
private RestTemplate restTemplate;
@PostMapping("/users")
public User createUser(@RequestBody User user) {
BlackList blackList =
loadBalanced.getForObject(blackListUrl, BlackList.class);
ValidationResult validationResult =
loadBalanced.getForObject(tcknValidationUrl, ValidationResult.class);
// do stuff with both objects
return user;
}
}
@RestController
public class SampleController {
@Autowired
private AsyncRestTemplate asyncRestTemplate;
@PostMapping("/users")
public User createUser(@RequestBody User user) {
ListenableFuture<ResponseEntity<BlackList>> future1 = asyncRestTemplate
.getForEntity(blackListUrl, BlackList.class);
ListenableFuture<ResponseEntity<ValidationResult>> future2 = asyncRestTemplate
.getForEntity(tcknValidationUrl, ValidationResult.class);
BlackList blackList = future1.get().getBody();
ValidationResult validationResult = future2.get().getBody();
// do stuff with both objects
return user;
}
}
@RestController
public class SampleController {
@Autowired
private WebClient webClient;
@PostMapping("/users")
public Mono<User> createUser(@RequestBody User user) {
Mono<BlackList> blackList = webClient
.exchange(blackListRequest)
.then(response -> response.body(toMono(User.class)));
Mono<ValidationResult> validationResult = webClient
.exchange(tcknValidationRequest)
.then(response -> response.body(toMono(Account.class)));
Mono<User> user =
Mono.zip(values -> { /*do stuff with values[0] and values[1] */ }), user, account);
return user;
}
}
Image source: https://developers.redhat.com/blog/2016/12/09/spring-cloud-for-microservices-compared-to-kubernetes/
destan@kodgemisi.com
By KodGemisi