├── README.md
├── gateway-service
└── main
│ └── gateway-service.go
├── profile-service
├── .DS_Store
├── pom.xml
└── src
│ └── main
│ ├── java
│ └── io
│ │ └── interviewready
│ │ └── profile
│ │ ├── ProfileApplication.java
│ │ ├── controller
│ │ └── ProfileService.java
│ │ ├── database
│ │ └── DBClient.java
│ │ ├── health
│ │ └── HeartBeat.java
│ │ └── models
│ │ ├── Profile.java
│ │ └── serviceclient
│ │ ├── Registration.java
│ │ └── ServiceNode.java
│ └── resources
│ └── application.properties
└── service-registry
├── pom.xml
└── src
└── main
├── java
└── io
│ └── interviewready
│ └── registry
│ ├── RegistryApplication.java
│ ├── database
│ └── DBClient.java
│ ├── heartbeat
│ └── HeartBeat.java
│ ├── models
│ ├── Registration.java
│ └── ServiceNode.java
│ └── service
│ └── ServiceRegistry.java
└── resources
└── application.properties
/README.md:
--------------------------------------------------------------------------------
1 | # microservices-core
2 |
3 | This live session describes three simple services used in most distributed microservice architecture systems. We use Go for creating a reverse proxy and Java for service discovery and registering profiles.
4 |
5 | The services use heartbeats to stay registered. We use the MySQL database in service registry and profile. The applications are written using Spring Boot, with their jar files running in AWS.
6 |
7 | The Gateway service is written in Go. Every service registers it's IP and ports to the service registry. The EC2 instance running the gateway and profile services is different from the one hosting the service registry.
8 |
9 | We confirm that the services work using a GET and POST request using curl from the local machine.
10 |
11 | Video: https://www.youtube.com/watch?v=atCbbvKOKnU
12 |
13 | System Design Video Course: https://interviewready.io
14 | System Design book - https://amzn.to/2yQIrxH
15 | System Design Playlist: https://www.youtube.com/playlist?list=PLMCXHnjXnTnvo6alSjVkgxV-VH6EPyvoX
16 |
17 | You can follow me on:
18 | Quora: https://www.quora.com/profile/Gaurav-Sen-6
19 | LinkedIn: https://www.linkedin.com/in/gaurav-sen-56b6a941/
20 | Twitter: https://twitter.com/gkcs_
21 |
--------------------------------------------------------------------------------
/gateway-service/main/gateway-service.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "fmt"
7 | "io/ioutil"
8 | "log"
9 | "net"
10 | "net/http"
11 | "strconv"
12 |
13 | mux "github.com/gorilla/mux"
14 | uuid "github.com/satori/go.uuid"
15 | )
16 |
17 | func main() {
18 | registerService()
19 | fmt.Println("Registered service")
20 | registerNode()
21 | fmt.Println("Registered Node")
22 | go respondToHealthChecks()
23 | fmt.Println("Set health checks")
24 | respondToExternalRequests()
25 | }
26 |
27 | func registerService() {
28 | values := make(map[string]interface{})
29 | values["serviceName"] = "gateway"
30 | values["methodNames"] = [1]string{"blacklist"}
31 | jsonValue, _ := json.Marshal(values)
32 | resp, err := http.Post("http://172.31.53.254:1234/service/register", "application/json", bytes.NewBuffer(jsonValue))
33 | //resp, err := http.Post("http://localhost:1234/service/register", "application/json", bytes.NewBuffer(jsonValue))
34 | if err != nil {
35 | panic(err)
36 | }
37 | defer resp.Body.Close()
38 | if resp.StatusCode == http.StatusOK {
39 | bodyBytes, _ := ioutil.ReadAll(resp.Body)
40 | bodyString := string(bodyBytes)
41 | fmt.Println(bodyString)
42 | }
43 | }
44 |
45 | func registerNode() {
46 | identifier, _ := uuid.NewV4()
47 | outboundIP := GetOutboundIP().String()
48 | fmt.Println(outboundIP)
49 | values := map[string]interface{}{"id": identifier.String(), "ipAddress": outboundIP, "serviceName": "gateway", "port": 5000}
50 | jsonValue, _ := json.Marshal(values)
51 | req, err := http.NewRequest("PUT", "http://172.31.53.254:1234/node", bytes.NewBuffer(jsonValue))
52 | //req, err := http.NewRequest("PUT", "http://localhost:1234/node", bytes.NewBuffer(jsonValue))
53 | if err != nil {
54 | return
55 | }
56 | req.Header.Set("Content-Type", "application/json")
57 | resp, err := http.DefaultClient.Do(req)
58 | if err != nil {
59 | panic(err)
60 | }
61 | defer resp.Body.Close()
62 | if resp.StatusCode == http.StatusOK {
63 | bodyBytes, _ := ioutil.ReadAll(resp.Body)
64 | bodyString := string(bodyBytes)
65 | fmt.Println(bodyString)
66 | }
67 | }
68 |
69 | func respondToHealthChecks() {
70 | router := mux.NewRouter().StrictSlash(true)
71 | router.HandleFunc("/health", blank)
72 | log.Fatal(http.ListenAndServe(":5000", router))
73 | }
74 |
75 | func blank(w http.ResponseWriter, r *http.Request) {
76 | fmt.Fprintf(w, "")
77 | }
78 |
79 | func respondToExternalRequests() {
80 | router := mux.NewRouter().StrictSlash(true)
81 | router.HandleFunc("/{methodName}", route)
82 | log.Fatal(http.ListenAndServe(":5002", router))
83 | }
84 |
85 | func route(w http.ResponseWriter, r *http.Request) {
86 | vars := mux.Vars(r)
87 | url := "http://172.31.53.254:1234/getHandlers?methodName=" + vars["methodName"]
88 | //url := "http://localhost:1234/getHandlers?methodName=" + vars["methodName"]
89 | fmt.Println(r.Method)
90 | fmt.Println(url)
91 | resp, err := http.Get(url)
92 | if err != nil {
93 | panic(err)
94 | }
95 | defer resp.Body.Close()
96 | if resp.StatusCode == http.StatusOK {
97 | var nodes []Node
98 | json.NewDecoder(resp.Body).Decode(&nodes)
99 | node := nodes[0]
100 | fmt.Println(nodes)
101 | redirectURL := "http://" + node.IPAddress + ":" + strconv.Itoa(node.Port) + "/" + vars["methodName"] + "?" + r.URL.RawQuery
102 | fmt.Println(redirectURL)
103 | redirectRequest, err := http.NewRequest(r.Method, redirectURL, r.Body)
104 | if err != nil {
105 | return
106 | }
107 | redirectRequest.Header.Set("Content-Type", "application/json")
108 | redirectResponse, err := http.DefaultClient.Do(redirectRequest)
109 | if err != nil {
110 | panic(err)
111 | }
112 | defer redirectResponse.Body.Close()
113 | if redirectResponse.StatusCode == http.StatusOK {
114 | bodyBytes, _ := ioutil.ReadAll(redirectResponse.Body)
115 | bodyString := string(bodyBytes)
116 | fmt.Println(bodyString)
117 | fmt.Fprintf(w, bodyString)
118 | }
119 | }
120 | }
121 |
122 | //Node is a service node in the system
123 | type Node struct {
124 | ID string `json:"id"`
125 | IPAddress string `json:"ipAddress"`
126 | ServiceName string `json:"serviceName"`
127 | Port int `json:"port"`
128 | }
129 |
130 | func (n *Node) String() string {
131 | return n.IPAddress + " " + n.ServiceName + " " + n.ID
132 | }
133 |
134 | //GetOutboundIP Get preferred outbound ip of this machine
135 | func GetOutboundIP() net.IP {
136 | conn, err := net.Dial("udp", "8.8.8.8:80")
137 | if err != nil {
138 | log.Fatal(err)
139 | }
140 | defer conn.Close()
141 |
142 | localAddr := conn.LocalAddr().(*net.UDPAddr)
143 |
144 | return localAddr.IP
145 | }
146 |
--------------------------------------------------------------------------------
/profile-service/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coding-parrot/microservices-core/bfe5a71105e9fcf2d8a1f5ee3a1a7551032fdbd2/profile-service/.DS_Store
--------------------------------------------------------------------------------
/profile-service/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | microservices-core
8 | profile-service
9 | 1.0
10 | jar
11 |
12 |
13 |
14 |
15 | org.apache.maven.plugins
16 | maven-compiler-plugin
17 |
18 | 11
19 | 11
20 |
21 |
22 |
23 | org.springframework.boot
24 | spring-boot-maven-plugin
25 |
26 | true
27 | io.interviewready.profile.ProfileApplication
28 |
29 |
30 |
31 |
32 | repackage
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | org.springframework
43 | spring-core
44 | 5.2.22.RELEASE
45 |
46 |
47 | org.springframework
48 | spring-context
49 | 5.2.7.RELEASE
50 |
51 |
52 | org.springframework
53 | spring-web
54 | 5.2.7.RELEASE
55 |
56 |
57 | org.springframework.boot
58 | spring-boot-starter-web
59 | 2.5.12
60 |
61 |
62 | mysql
63 | mysql-connector-java
64 | 8.0.28
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/profile-service/src/main/java/io/interviewready/profile/ProfileApplication.java:
--------------------------------------------------------------------------------
1 | package io.interviewready.profile;
2 |
3 | import com.fasterxml.jackson.core.JsonProcessingException;
4 | import com.fasterxml.jackson.databind.ObjectMapper;
5 | import com.fasterxml.jackson.databind.ObjectWriter;
6 | import io.interviewready.profile.models.serviceclient.Registration;
7 | import io.interviewready.profile.models.serviceclient.ServiceNode;
8 | import org.springframework.boot.SpringApplication;
9 | import org.springframework.boot.autoconfigure.SpringBootApplication;
10 |
11 | import java.net.InetAddress;
12 | import java.net.URI;
13 | import java.net.UnknownHostException;
14 | import java.net.http.HttpClient;
15 | import java.net.http.HttpRequest;
16 | import java.net.http.HttpResponse;
17 | import java.util.UUID;
18 |
19 | @SpringBootApplication
20 | public class ProfileApplication {
21 | public static void main(String[] args) throws UnknownHostException, JsonProcessingException {
22 | final HttpClient client = HttpClient.newHttpClient();
23 | final ObjectWriter objectWriter = new ObjectMapper().writer().withDefaultPrettyPrinter();
24 | final Registration registration = new Registration("profile", new String[]{"profile", "register"});
25 | final String registrationJson = objectWriter.writeValueAsString(registration);
26 | final HttpRequest registerServiceRequest = HttpRequest.newBuilder()
27 | .uri(URI.create("http://localhost:1234/service/register"))
28 | .header("Content-Type", "application/json")
29 | .POST(HttpRequest.BodyPublishers.ofString(registrationJson))
30 | .build();
31 | final ServiceNode profile = new ServiceNode(UUID.randomUUID().toString(), InetAddress.getLocalHost().getHostAddress(), "profile", 5001);
32 | final String profileJson = objectWriter.writeValueAsString(profile);
33 | System.out.println(profileJson);
34 | final HttpRequest registerNodeRequest = HttpRequest.newBuilder()
35 | .uri(URI.create("http://localhost:1234/node"))
36 | .header("Content-Type", "application/json")
37 | .PUT(HttpRequest.BodyPublishers.ofString(profileJson))
38 | .build();
39 | client.sendAsync(registerServiceRequest, HttpResponse.BodyHandlers.ofString())
40 | .thenApply(HttpResponse::body)
41 | .thenAccept(System.out::println)
42 | .thenCompose(__ -> client.sendAsync(registerNodeRequest, HttpResponse.BodyHandlers.ofString()))
43 | .thenApply(HttpResponse::body)
44 | .thenAccept(System.out::println)
45 | .join();
46 | SpringApplication.run(ProfileApplication.class, args);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/profile-service/src/main/java/io/interviewready/profile/controller/ProfileService.java:
--------------------------------------------------------------------------------
1 | package io.interviewready.profile.controller;
2 |
3 | import io.interviewready.profile.database.DBClient;
4 | import io.interviewready.profile.models.Profile;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.web.bind.annotation.*;
7 |
8 | import java.sql.SQLException;
9 |
10 | @RestController
11 | public class ProfileService {
12 | private final DBClient databaseClient;
13 |
14 | @Autowired
15 | public ProfileService(final DBClient databaseClient) {
16 | this.databaseClient = databaseClient;
17 | }
18 |
19 | @GetMapping("/profile")
20 | public Profile getProfile(@RequestParam final String id) {
21 | return databaseClient.getProfile(id);
22 | }
23 |
24 | @PostMapping(value = "/register", consumes = "application/json")
25 | public void register(@RequestBody Profile profile) {
26 | databaseClient.addProfile(profile);
27 | }
28 | }
29 |
30 |
--------------------------------------------------------------------------------
/profile-service/src/main/java/io/interviewready/profile/database/DBClient.java:
--------------------------------------------------------------------------------
1 | package io.interviewready.profile.database;
2 |
3 |
4 | import com.mysql.cj.jdbc.MysqlDataSource;
5 | import io.interviewready.profile.models.Profile;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.context.annotation.Bean;
8 | import org.springframework.stereotype.Repository;
9 |
10 | import java.sql.*;
11 |
12 | @Repository
13 | public class DBClient {
14 |
15 | private final Connection connection;
16 |
17 | @Autowired
18 | public DBClient() throws SQLException {
19 | MysqlDataSource dataSource = new MysqlDataSource();
20 | dataSource.setURL("jdbc:mysql://localhost/profile_db");
21 | dataSource.setUser("gaurav");
22 | dataSource.setPassword("gaurav");
23 | connection = dataSource.getConnection();
24 | }
25 |
26 | public void addProfile(final Profile profile) {
27 | try {
28 | final PreparedStatement insertProfile = connection.prepareStatement("insert into profiles(id,first_name,last_name,password,imageUrl) values (?,?,?,?,?)");
29 | insertProfile.setString(1, profile.getUserId());
30 | insertProfile.setString(2, profile.getFirstName());
31 | insertProfile.setString(3, profile.getLastName());
32 | insertProfile.setString(4, profile.getPassword());
33 | insertProfile.setString(5, profile.getImageUrl());
34 | insertProfile.execute();
35 | } catch (SQLException throwable) {
36 | throwable.printStackTrace();
37 | }
38 | }
39 |
40 | public Profile getProfile(final String id) {
41 | try {
42 | PreparedStatement preparedStatement = connection.prepareStatement("select * from profiles where id=?");
43 | preparedStatement.setString(1, id);
44 | ResultSet rs = preparedStatement.executeQuery();
45 | rs.next();
46 | return new Profile(rs.getString(1), rs.getString(2), rs.getString(3), rs.getString(4), rs.getString(5));
47 | } catch (SQLException throwable) {
48 | throwable.printStackTrace();
49 | throw new RuntimeException(throwable);
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/profile-service/src/main/java/io/interviewready/profile/health/HeartBeat.java:
--------------------------------------------------------------------------------
1 | package io.interviewready.profile.health;
2 |
3 | import org.springframework.context.annotation.Lazy;
4 | import org.springframework.web.bind.annotation.GetMapping;
5 | import org.springframework.web.bind.annotation.RestController;
6 |
7 | @RestController
8 | public class HeartBeat {
9 | @GetMapping("/health")
10 | public String healthCheck() {
11 | System.out.println("Got a health check!");
12 | return "";
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/profile-service/src/main/java/io/interviewready/profile/models/Profile.java:
--------------------------------------------------------------------------------
1 | package io.interviewready.profile.models;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 |
6 | public class Profile {
7 | private final String userId;
8 | private final String firstName;
9 | private final String lastName;
10 | private final String password;
11 | private final String imageUrl;
12 |
13 | @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
14 | public Profile(@JsonProperty("userId") final String userId,
15 | @JsonProperty("firstName") final String firstName,
16 | @JsonProperty("lastName") final String lastName,
17 | @JsonProperty("password") final String password,
18 | @JsonProperty("imageUrl") final String imageUrl) {
19 | this.userId = userId;
20 | this.firstName = firstName;
21 | this.lastName = lastName;
22 | this.password = password;
23 | this.imageUrl = imageUrl;
24 | }
25 |
26 | public String getUserId() {
27 | return userId;
28 | }
29 |
30 | public String getFirstName() {
31 | return firstName;
32 | }
33 |
34 | public String getLastName() {
35 | return lastName;
36 | }
37 |
38 | public String getPassword() {
39 | return password;
40 | }
41 |
42 | public String getImageUrl() {
43 | return imageUrl;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/profile-service/src/main/java/io/interviewready/profile/models/serviceclient/Registration.java:
--------------------------------------------------------------------------------
1 | package io.interviewready.profile.models.serviceclient;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 |
6 | public class Registration {
7 |
8 | private final String serviceName;
9 | private final String[] methodNames;
10 |
11 | @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
12 | public Registration(@JsonProperty("serviceName") final String serviceName,
13 | @JsonProperty("methodNames") final String[] methodNames) {
14 | this.serviceName = serviceName;
15 | this.methodNames = methodNames;
16 | }
17 |
18 | public String getServiceName() {
19 | return serviceName;
20 | }
21 |
22 | public String[] getMethodNames() {
23 | return methodNames;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/profile-service/src/main/java/io/interviewready/profile/models/serviceclient/ServiceNode.java:
--------------------------------------------------------------------------------
1 | package io.interviewready.profile.models.serviceclient;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 |
6 | public class ServiceNode {
7 | private final String id;
8 | private final String ipAddress;
9 | private final String serviceName;
10 | private final int port;
11 |
12 | @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
13 | public ServiceNode(@JsonProperty("id") final String id,
14 | @JsonProperty("ipAddress") final String ipAddress,
15 | @JsonProperty("serviceName") final String serviceName,
16 | @JsonProperty("port") final int port) {
17 | this.id = id;
18 | this.ipAddress = ipAddress;
19 | this.serviceName = serviceName;
20 | this.port = port;
21 | }
22 |
23 | public String getId() {
24 | return id;
25 | }
26 |
27 | public String getIpAddress() {
28 | return ipAddress;
29 | }
30 |
31 | public String getServiceName() {
32 | return serviceName;
33 | }
34 |
35 | public int getPort() {
36 | return port;
37 | }
38 |
39 | @Override
40 | public String toString() {
41 | return "ServiceNode{" +
42 | "id='" + id + '\'' +
43 | ", ipAddress='" + ipAddress + '\'' +
44 | ", serviceName='" + serviceName + '\'' +
45 | ", port=" + port +
46 | '}';
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/profile-service/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | username=gaurav
2 | password=gaurav
3 | host=localhost
4 | database.port=3306
5 | server.port=5001
--------------------------------------------------------------------------------
/service-registry/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | microservices-core
8 | service-registry
9 | 1.0
10 | jar
11 |
12 |
13 |
14 |
15 | org.apache.maven.plugins
16 | maven-compiler-plugin
17 |
18 | 11
19 | 11
20 |
21 |
22 |
23 | org.springframework.boot
24 | spring-boot-maven-plugin
25 |
26 | true
27 | io.interviewready.registry.RegistryApplication
28 |
29 |
30 |
31 |
32 | repackage
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | org.springframework
43 | spring-core
44 | 5.2.22.RELEASE
45 |
46 |
47 | org.springframework
48 | spring-context
49 | 5.2.7.RELEASE
50 |
51 |
52 | org.springframework
53 | spring-web
54 | 5.2.7.RELEASE
55 |
56 |
57 | org.springframework.boot
58 | spring-boot-starter-web
59 | 2.5.12
60 |
61 |
62 | mysql
63 | mysql-connector-java
64 | 8.0.28
65 |
66 |
67 |
--------------------------------------------------------------------------------
/service-registry/src/main/java/io/interviewready/registry/RegistryApplication.java:
--------------------------------------------------------------------------------
1 | package io.interviewready.registry;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 |
6 | @SpringBootApplication
7 | public class RegistryApplication {
8 | public static void main(String[] args) {
9 | SpringApplication.run(RegistryApplication.class, args);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/service-registry/src/main/java/io/interviewready/registry/database/DBClient.java:
--------------------------------------------------------------------------------
1 | package io.interviewready.registry.database;
2 |
3 |
4 | import com.mysql.cj.jdbc.MysqlDataSource;
5 | import io.interviewready.registry.models.ServiceNode;
6 | import org.slf4j.Logger;
7 | import org.slf4j.LoggerFactory;
8 | import org.springframework.beans.factory.annotation.Autowired;
9 | import org.springframework.stereotype.Repository;
10 |
11 | import java.sql.Connection;
12 | import java.sql.PreparedStatement;
13 | import java.sql.ResultSet;
14 | import java.sql.SQLException;
15 | import java.util.ArrayList;
16 | import java.util.List;
17 | import java.util.concurrent.CompletableFuture;
18 |
19 | @Repository
20 | public class DBClient {
21 | private final Connection connection;
22 | private final Logger logger;
23 |
24 | @Autowired
25 | public DBClient() throws SQLException {
26 | MysqlDataSource dataSource = new MysqlDataSource();
27 | dataSource.setURL("jdbc:mysql://localhost/registry");
28 | dataSource.setUser("gaurav");
29 | dataSource.setPassword("gaurav");
30 | connection = dataSource.getConnection();
31 | logger = LoggerFactory.getLogger(DBClient.class.getCanonicalName());
32 | }
33 |
34 | public CompletableFuture addNode(final ServiceNode node) {
35 | try {
36 | final PreparedStatement insertNode = connection.prepareStatement("insert into node(id,ip_address,service_name,port) values (?,?,?,?)");
37 | insertNode.setString(1, node.getId());
38 | insertNode.setString(2, node.getIpAddress());
39 | insertNode.setString(3, node.getServiceName());
40 | insertNode.setInt(4, node.getPort());
41 | return CompletableFuture.runAsync(() -> {
42 | try {
43 | insertNode.execute();
44 | } catch (SQLException e) {
45 | throw new RuntimeException(e);
46 | }
47 | }).whenComplete((__, throwable) -> {
48 | if (throwable != null) {
49 | logger.error("exception inserting node record ", throwable);
50 | }
51 | });
52 | } catch (SQLException throwable) {
53 | logger.error("exception building insert statement ", throwable);
54 | return CompletableFuture.failedFuture(throwable);
55 | }
56 | }
57 |
58 | public CompletableFuture removeNode(final String id) {
59 | try {
60 | final PreparedStatement deleteNode = connection.prepareStatement("delete from node where id=?");
61 | deleteNode.setString(1, id);
62 | return CompletableFuture.runAsync(() -> {
63 | try {
64 | deleteNode.execute();
65 | } catch (SQLException e) {
66 | throw new RuntimeException(e);
67 | }
68 | }).whenComplete((__, throwable) -> {
69 | if (throwable != null) {
70 | logger.error("exception inserting node record ", throwable);
71 | }
72 | });
73 | } catch (SQLException throwable) {
74 | logger.error("exception building delete statement ", throwable);
75 | return CompletableFuture.failedFuture(throwable);
76 | }
77 | }
78 |
79 | public CompletableFuture> getServiceNodes(final String methodName) {
80 | try {
81 | final PreparedStatement getServiceNodes = connection.prepareStatement("select * from node where service_name = (select service_name from registration where method_name = ?)");
82 | getServiceNodes.setString(1, methodName);
83 | return CompletableFuture.supplyAsync(() -> {
84 | final ResultSet rs;
85 | try {
86 | rs = getServiceNodes.executeQuery();
87 | final List serviceNodes = new ArrayList<>();
88 | while (rs.next()) {
89 | serviceNodes.add(new ServiceNode(rs.getString(1), rs.getString(2), rs.getString(3), rs.getInt(4)));
90 | }
91 | return serviceNodes;
92 | } catch (SQLException throwable) {
93 | logger.error("exception building query ", throwable);
94 | throw new RuntimeException(throwable);
95 | }
96 | });
97 | } catch (SQLException throwable) {
98 | logger.error("exception building query ", throwable);
99 | return CompletableFuture.failedFuture(throwable);
100 | }
101 | }
102 |
103 | public CompletableFuture> getAllServiceNodes() {
104 | return CompletableFuture.supplyAsync(() -> {
105 | final ResultSet rs;
106 | try {
107 | rs = connection.prepareStatement("select * from node").executeQuery();
108 | final List serviceNodes = new ArrayList<>();
109 | while (rs.next()) {
110 | serviceNodes.add(new ServiceNode(rs.getString(1), rs.getString(2), rs.getString(3), rs.getInt(4)));
111 | }
112 | return serviceNodes;
113 | } catch (SQLException throwable) {
114 | logger.error("exception building query ", throwable);
115 | throw new RuntimeException(throwable);
116 | }
117 | });
118 | }
119 |
120 | public CompletableFuture register(final String serviceName, final String[] methodNames) {
121 | try {
122 | final PreparedStatement registerService = connection.prepareStatement("insert ignore into registration(method_name,service_name) values (?,?)");
123 | return CompletableFuture.runAsync(() -> {
124 | try {
125 | for (final String methodName : methodNames) {
126 | registerService.setString(1, methodName);
127 | registerService.setString(2, serviceName);
128 | registerService.addBatch();
129 | }
130 | registerService.executeBatch();
131 | } catch (SQLException e) {
132 | throw new RuntimeException(e);
133 | }
134 | }).whenComplete((__, throwable) -> {
135 | if (throwable != null) {
136 | logger.error("exception inserting node record ", throwable);
137 | }
138 | });
139 | } catch (SQLException throwable) {
140 | logger.error("exception building insert statement ", throwable);
141 | return CompletableFuture.failedFuture(throwable);
142 | }
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/service-registry/src/main/java/io/interviewready/registry/heartbeat/HeartBeat.java:
--------------------------------------------------------------------------------
1 | package io.interviewready.registry.heartbeat;
2 |
3 | import io.interviewready.registry.database.DBClient;
4 | import org.slf4j.Logger;
5 | import org.slf4j.LoggerFactory;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.context.annotation.Configuration;
8 | import org.springframework.scheduling.annotation.EnableScheduling;
9 | import org.springframework.scheduling.annotation.Scheduled;
10 | import org.springframework.stereotype.Component;
11 |
12 | import java.net.URI;
13 | import java.net.http.HttpClient;
14 | import java.net.http.HttpRequest;
15 | import java.net.http.HttpResponse;
16 | import java.util.concurrent.TimeUnit;
17 | import java.util.stream.Collectors;
18 |
19 | @Configuration
20 | @Component
21 | @EnableScheduling
22 | public class HeartBeat {
23 | private final DBClient dbClient;
24 | private final HttpClient httpClient;
25 | private final Logger logger;
26 |
27 | @Autowired
28 | public HeartBeat(final DBClient dbClient) {
29 | this.dbClient = dbClient;
30 | this.httpClient = HttpClient.newHttpClient();
31 | logger = LoggerFactory.getLogger(HeartBeat.class.getCanonicalName());
32 | }
33 |
34 | @Scheduled(fixedRate = 7500)
35 | public void heartbeat() {
36 | dbClient.getAllServiceNodes()
37 | .thenApply(serviceNodes -> serviceNodes.stream()
38 | .map(serviceNode -> {
39 | final String url = "http://" + serviceNode.getIpAddress() + ":" + serviceNode.getPort() + "/health";
40 | System.out.println(url);
41 | final HttpRequest httpRequest = HttpRequest.newBuilder()
42 | .uri(URI.create(url))
43 | .build();
44 | return httpClient.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofString())
45 | .orTimeout(1, TimeUnit.SECONDS)
46 | .thenApply(HttpResponse::statusCode)
47 | .whenComplete((statusCode, throwable) -> {
48 | if (throwable != null || statusCode != 200) {
49 | if (throwable != null) {
50 | logger.error("", throwable);
51 | } else {
52 | logger.error(serviceNode + " is UNRESPONSIVE!" + statusCode);
53 | }
54 | dbClient.removeNode(serviceNode.getId());
55 | } else {
56 | logger.info(serviceNode.getId() + " is alive.");
57 | }
58 | });
59 | }).collect(Collectors.toList())).join();
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/service-registry/src/main/java/io/interviewready/registry/models/Registration.java:
--------------------------------------------------------------------------------
1 | package io.interviewready.registry.models;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 |
6 | public class Registration {
7 |
8 | private final String serviceName;
9 | private final String[] methodNames;
10 |
11 | @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
12 | public Registration(@JsonProperty("serviceName") final String serviceName,
13 | @JsonProperty("methodNames") final String[] methodNames) {
14 | this.serviceName = serviceName;
15 | this.methodNames = methodNames;
16 | }
17 |
18 | public String getServiceName() {
19 | return serviceName;
20 | }
21 |
22 | public String[] getMethodNames() {
23 | return methodNames;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/service-registry/src/main/java/io/interviewready/registry/models/ServiceNode.java:
--------------------------------------------------------------------------------
1 | package io.interviewready.registry.models;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 |
6 | public class ServiceNode {
7 | private final String id;
8 | private final String ipAddress;
9 | private final String serviceName;
10 | private final int port;
11 |
12 | @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
13 | public ServiceNode(@JsonProperty("id") final String id,
14 | @JsonProperty("ipAddress") final String ipAddress,
15 | @JsonProperty("serviceName") final String serviceName,
16 | @JsonProperty("port") final int port) {
17 | this.id = id;
18 | this.ipAddress = ipAddress;
19 | this.serviceName = serviceName;
20 | this.port = port;
21 | }
22 |
23 | public String getId() {
24 | return id;
25 | }
26 |
27 | public String getIpAddress() {
28 | return ipAddress;
29 | }
30 |
31 | public String getServiceName() {
32 | return serviceName;
33 | }
34 |
35 | public int getPort() {
36 | return port;
37 | }
38 |
39 | @Override
40 | public String toString() {
41 | return "ServiceNode{" +
42 | "id='" + id + '\'' +
43 | ", ipAddress='" + ipAddress + '\'' +
44 | ", serviceName='" + serviceName + '\'' +
45 | ", port=" + port +
46 | '}';
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/service-registry/src/main/java/io/interviewready/registry/service/ServiceRegistry.java:
--------------------------------------------------------------------------------
1 | package io.interviewready.registry.service;
2 |
3 | import io.interviewready.registry.database.DBClient;
4 | import io.interviewready.registry.models.Registration;
5 | import io.interviewready.registry.models.ServiceNode;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.web.bind.annotation.*;
8 |
9 | import java.util.List;
10 |
11 | @RestController
12 | public class ServiceRegistry {
13 | private final DBClient dbClient;
14 |
15 | @Autowired
16 | public ServiceRegistry(final DBClient dbClient) {
17 | this.dbClient = dbClient;
18 | }
19 |
20 | @GetMapping("/getHandlers")
21 | public List getHandlers(@RequestParam final String methodName) {
22 | return dbClient.getServiceNodes(methodName).join();
23 | }
24 |
25 | @PutMapping("/node")
26 | public void addNode(@RequestBody final ServiceNode serviceNode) {
27 | dbClient.addNode(serviceNode);
28 | }
29 |
30 | @PostMapping("/service/register")
31 | public void register(@RequestBody final Registration registration) {
32 | dbClient.register(registration.getServiceName(), registration.getMethodNames());
33 | }
34 |
35 | @DeleteMapping("/node")
36 | public void deleteNode(@RequestParam final String id) {
37 | dbClient.removeNode(id);
38 | }
39 | }
40 |
41 |
--------------------------------------------------------------------------------
/service-registry/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | server.port=1234
--------------------------------------------------------------------------------