├── 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 --------------------------------------------------------------------------------