在〈gossip 服務(二)拆分〉中,已經分出了 Mail、Account 與 Message 服務,實際上,組態伺服器也是個服務,若想要在運用這些服務時有伸縮的彈性,可以建立服務註冊伺服器,將這些服務註冊上去,需要服務的就到服務註冊伺服器上查找服務,也就是接下來,打算採取底下的架構:
服務註冊伺服器就使用〈服務註冊伺服器〉的成果就可以了,因為只是範例,就不特別像〈服務可用性〉中談到的,特別還要建立多個服務註冊伺服器並彼此複製註冊表了。
因為組態伺服器也打算註冊為服務,因此首先要處理的,就是先修改 configsvi 的 bootstrap.properties:
server.port=8888
spring.application.name=configsvr
spring.cloud.config.server.encrypt.enabled=false
eureka.client.fetchRegistry=false
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/
eureka.instance.preferIpAddress=true
Mail、Account 與 Message 服務,基本上也要加上註冊伺服器的設定,以 acctsvi 的 bootstrap.properties 為例:
server.port=8084
spring.application.name=acctsvi
spring.profiles.active=default
# spring.cloud.config.uri=http://localhost:8888
spring.cloud.config.discovery.enabled=true
spring.cloud.config.discovery.serviceId=configsvr
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/
eureka.instance.preferIpAddress=true
因為現在要從服務註冊伺服器上取得組態伺服器的資訊,記得使用 spring.cloud.config.discovery
相關設定,而不是 spring.cloud.config.uri
。
當然,別忘了 build.gradle 上都得加上必要的相依。接下來,gossip 的 bootstrap.properties 加上服務註冊伺服器的設定,以便查找服務:
spring.application.name=gossip
spring.profiles.active=default
eureka.client.registerWithEureka=false
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/
然後,AccountService
、EmailService
以及 MessageService
的實作,URI 的部份就可以改成各自對應的服務名稱了。
不過,AccountService
、EmailService
以及 MessageService
的實作,幾乎都像是樣版般的程式碼,這就想到了〈RestTemplate 與 Ribbon/Feign〉中談到,可以使用 Feign 來宣告服務的請求。
因此,可以在 build.gradle 中加入必要的相依,然後,GossipApplication
加註 @EnableFeignClients
,因為現在身為 Feign 客戶端的 AccountService
、EmailService
以及 MessageService
,不在同一個套件之中,記得使用 basePackages
來指定它們的位置:
...略
@EnableFeignClients(
basePackages={
"cc.openhome.model"
}
)
@PropertySource("classpath:path.properties")
public class GossipApplication {
...略
}
然後,就可以分別進行宣告了,以 AccountService
為例:
package cc.openhome.model;
...
@FeignClient("acctsvi")
public interface AccountService {
@PostMapping("tryCreateUser?email={email}&username={username}&password={password}")
Resource<Account> tryCreateUser(@PathVariable("email") String email, @PathVariable("username") String username, @PathVariable("password") String password);
@GetMapping("userExisted?username={username}")
boolean userExisted(@PathVariable("username") String username);
@PutMapping("verify?email={email}&token={token}")
Resource<Account> verify(@PathVariable("email") String email, @PathVariable("token") String token);
@GetMapping("accountByNameEmail?username={username}&email={email}")
Resource<Account> accountByNameEmail(@PathVariable("username") String name, @PathVariable("email") String email);
@PutMapping("resetPassword?username={username}&password={password}")
void resetPassword(@PathVariable("username") String name, @PathVariable("password") String password);
@GetMapping("accountByName?username={username}")
Optional<Account> accountByName(@PathVariable("username") String username);
}
注意,這邊改變了介面上的方法簽署,以符合 Feign 的轉換規則,這就與 Account 服務上頭的 AccountService
不同了,這並不會怎樣,因為 Account 服務已經是個獨立的服務了,重點在於 REST 介面定義好就可以了。
在〈gossip 服務(二)拆分〉談過,Resource<Optional<Account>>
可以被轉換為 JSON,根據該 JSON 格式,服務的客戶端可以用 Resource<Account>
來取得,因此上面看到的 AccountService
中,傳回值是 Resource<Account>
,如果 JSON 本身沒有 Account
相關的屬性,那 getContent
時會是 null
。
因為修改了 AccountService
的方法簽署,相關控制器中也要做出修改,並且可以使用 Optional.ofNullable
來銜接。例如 AccountController
:
package cc.openhome.controller;
...略
@Controller
@SessionAttributes("token")
public class AccountController {
...略
@PostMapping("register")
public String register(
@Valid RegisterForm form,
BindingResult bindingResult,
Model model) {
List<String> errors = toList(bindingResult);
String path;
if(errors.isEmpty()) {
path = REGISTER_SUCCESS_PATH;
Optional<Account> optionalAcct = Optional.ofNullable(accountService.tryCreateUser(
form.getEmail(), form.getUsername(), form.getPassword()).getContent());
if(optionalAcct.isPresent()) {
emailService.validationLink(optionalAcct.get());
} else {
emailService.failedRegistration(
form.getUsername(), form.getEmail());
}
} else {
path = REGISTER_FORM_PATH;
model.addAttribute("errors", errors);
}
return path;
}
...略
}
你可以在 GossipSvi/3rd 中,找到全部修改完成的範例專案。