Spring Cloud - Bootstrapping

1. Übersicht

Spring Cloud ist ein Framework zum Erstellen robuster Cloud-Anwendungen. Das Framework erleichtert die Entwicklung von Anwendungen, indem es Lösungen für viele der häufigsten Probleme bietet, die beim Wechsel in eine verteilte Umgebung auftreten.

Anwendungen, die mit einer Microservices-Architektur ausgeführt werden, sollen die Entwicklung, Bereitstellung und Wartung vereinfachen. Die Zerlegung der Anwendung ermöglicht es Entwicklern, sich jeweils auf ein Problem zu konzentrieren. Verbesserungen können eingeführt werden, ohne andere Teile eines Systems zu beeinträchtigen.

Auf der anderen Seite ergeben sich unterschiedliche Herausforderungen, wenn wir einen Microservice-Ansatz verfolgen:

  • Externalisierung der Konfiguration, damit diese flexibel ist und bei Änderungen keine Neuerstellung des Dienstes erforderlich ist
  • Serviceerkennung
  • Ausblenden der Komplexität von Diensten, die auf verschiedenen Hosts bereitgestellt werden

In diesem Artikel werden fünf Microservices erstellt: ein Konfigurationsserver, ein Discovery-Server, ein Gateway-Server, ein Buchdienst und schließlich ein Bewertungsdienst. Diese fünf Mikrodienste bilden eine solide Basisanwendung, um mit der Cloud-Entwicklung zu beginnen und die oben genannten Herausforderungen zu bewältigen.

2. Server konfigurieren

Bei der Entwicklung einer Cloud-Anwendung besteht ein Problem darin, die Konfiguration zu verwalten und an unsere Services zu verteilen. Wir möchten wirklich keine Zeit damit verbringen, jede Umgebung zu konfigurieren, bevor wir unseren Service horizontal skalieren, oder Sicherheitsverletzungen riskieren, indem wir unsere Konfiguration in unsere Anwendung einbinden.

Um dies zu lösen, werden wir unsere gesamte Konfiguration in einem einzigen Git-Repository konsolidieren und diese mit einer Anwendung verbinden, die eine Konfiguration für alle unsere Anwendungen verwaltet. Wir werden eine sehr einfache Implementierung einrichten.

Weitere Informationen und ein komplexeres Beispiel finden Sie in unserem Artikel zur Spring Cloud-Konfiguration.

2.1. Konfiguration

Navigieren Sie zu //start.spring.io und wählen Sie Maven und Spring Boot 2.2.x aus.

Stellen Sie das Artefakt auf „config . Suchen Sie im Abschnitt "Abhängigkeiten" nach "Konfigurationsserver" und fügen Sie dieses Modul hinzu. Klicken Sie dann auf die Schaltfläche " Generieren" und wir können eine Zip-Datei mit einem vorkonfigurierten Projekt herunterladen und loslegen.

Alternativ können wir ein Spring Boot- Projekt generieren und der POM-Datei manuell einige Abhängigkeiten hinzufügen.

Diese Abhängigkeiten werden von allen Projekten gemeinsam genutzt:

 org.springframework.boot spring-boot-starter-parent 2.2.6.RELEASE     org.springframework.boot spring-boot-starter-test test      org.springframework.cloud spring-cloud-dependencies Hoxton.SR4 pom import       org.springframework.boot spring-boot-maven-plugin   

Fügen wir eine Abhängigkeit für den Konfigurationsserver hinzu:

 org.springframework.cloud spring-cloud-config-server 

Als Referenz finden wir die neueste Version von Maven Central ( Spring-Cloud-Abhängigkeiten, Test, Konfigurationsserver ).

2.2. Frühlingskonfiguration

Um den Konfigurationsserver zu aktivieren, müssen wir der Hauptanwendungsklasse einige Anmerkungen hinzufügen:

@SpringBootApplication @EnableConfigServer public class ConfigApplication {...}

@EnableConfigServer verwandelt unsere Anwendung in einen Konfigurationsserver.

2.3. Eigenschaften

Fügen wir die application.properties in src / main / resources hinzu :

server.port=8081 spring.application.name=config spring.cloud.config.server.git.uri=file://${user.home}/application-config

Die wichtigste Einstellung für den Konfigurationsserver ist der Parameter git.uri . Dies ist derzeit auf einen relativen Dateipfad festgelegt, der unter Windows oder / Users / {Benutzername} / unter * nix im Allgemeinen in c: \ Benutzer \ {Benutzername} \ aufgelöst wird . Diese Eigenschaft verweist auf ein Git-Repository, in dem die Eigenschaftendateien für alle anderen Anwendungen gespeichert sind. Bei Bedarf kann ein absoluter Dateipfad festgelegt werden.

Tipp : Stellen Sie auf einem Windows-Computer dem Wert "file: ///" voran, verwenden Sie unter * nix "file: //".

2.4. Git Repository

Navigieren Sie zu dem durch spring.cloud.config.server.git.uri definierten Ordner und fügen Sie den Ordner application-config hinzu . CD in diesen Ordner und geben Sie git init ein . Dadurch wird ein Git-Repository initialisiert, in dem wir Dateien speichern und deren Änderungen verfolgen können.

2.5. Lauf

Lassen Sie uns den Konfigurationsserver ausführen und sicherstellen, dass er funktioniert. Geben Sie in der Befehlszeile mvn spring-boot: run ein . Dadurch wird der Server gestartet.

Diese Ausgabe sollte anzeigen, dass der Server ausgeführt wird:

Tomcat started on port(s): 8081 (http)

2.6. Bootstrapping-Konfiguration

In unseren nachfolgenden Servern möchten wir, dass ihre Anwendungseigenschaften von diesem Konfigurationsserver verwaltet werden. Dazu müssen wir tatsächlich ein bisschen Hühnchen-und-Ei machen: Konfigurieren Sie in jeder Anwendung Eigenschaften, die wissen, wie man mit diesem Server zurück spricht.

Es ist ein Bootstrap-Prozess, und jede dieser Apps wird eine Datei namens bootstrap.properties haben . Es enthält Eigenschaften wie application.properties, jedoch mit einer Wendung:

Ein übergeordneter Spring ApplicationContext lädt zuerst die Datei bootstrap.properties . Dies ist wichtig, damit Config Server mit der Verwaltung der Eigenschaften in application.properties beginnen kann . Es ist dieser spezielle ApplicationContext , der auch alle verschlüsselten Anwendungseigenschaften entschlüsselt.

Es ist klug, diese Eigenschaftendateien getrennt zu halten. bootstrap.properties dient zum Bereitstellen des Konfigurationsservers und application.properties für anwendungsspezifische Eigenschaften. Technisch ist es jedoch möglich, Anwendungseigenschaften in bootstrap.properties zu platzieren .

Da Config Server unsere Anwendungseigenschaften verwaltet, könnte man sich fragen, warum überhaupt eine application.properties vorhanden ist . Die Antwort ist, dass diese immer noch als Standardwerte nützlich sind, die Config Server möglicherweise nicht hat.

3. Entdeckung

Now that we have configuration taken care of, we need a way for all of our servers to be able to find each other. We will solve this problem by setting the Eureka discovery server up. Since our applications could be running on any ip/port combination we need a central address registry that can serve as an application address lookup.

When a new server is provisioned it will communicate with the discovery server and register its address so that others can communicate with it. This way other applications can consume this information as they make requests.

To learn more details and see a more complex discovery implementation take a look at Spring Cloud Eureka article.

3.1. Setup

Again we'll navigate to start.spring.io. Set the artifact to “discovery”. Search for “eureka server” and add that dependency. Search for “config client” and add that dependency. Finally, generate the project.

Alternatively, we can create a Spring Boot project, copy the contents of the POM from config server and swap in these dependencies:

 org.springframework.cloud spring-cloud-starter-config   org.springframework.cloud spring-cloud-starter-eureka-server 

For reference, we'll find the bundles on Maven Central (config-client, eureka-server).

3.2. Spring Config

Let's add Java config to the main class:

@SpringBootApplication @EnableEurekaServer public class DiscoveryApplication {...}

@EnableEurekaServer will configure this server as a discovery server using Netflix Eureka. Spring Boot will automatically detect the configuration dependency on the classpath and lookup the configuration from the config server.

3.3. Properties

Now we will add two properties files:

First, we add bootstrap.properties into src/main/resources:

spring.cloud.config.name=discovery spring.cloud.config.uri=//localhost:8081

These properties will let discovery server query the config server at startup.

And second, we add discovery.properties to our Git repository

spring.application.name=discovery server.port=8082 eureka.instance.hostname=localhost eureka.client.serviceUrl.defaultZone=//localhost:8082/eureka/ eureka.client.register-with-eureka=false eureka.client.fetch-registry=false

The filename must match the spring.application.name property.

In addition, we are telling this server that it is operating in the default zone, this matches the config client's region setting. We are also telling the server not to register with another discovery instance.

In production, we'd have more than one of these to provide redundancy in the event of failure and that setting would be true.

Let's commit the file to the Git repository. Otherwise, the file will not be detected.

3.4. Add Dependency to the Config Server

Add this dependency to the config server POM file:

 org.springframework.cloud spring-cloud-starter-eureka 

For reference, we can find the bundle on Maven Central (eureka-client).

Add these properties to the application.properties file in src/main/resources of the config server:

eureka.client.region = default eureka.client.registryFetchIntervalSeconds = 5 eureka.client.serviceUrl.defaultZone=//localhost:8082/eureka/

3.5. Run

Start the discovery server using the same command, mvn spring-boot:run. The output from the command line should include:

Fetching config from server at: //localhost:8081 ... Tomcat started on port(s): 8082 (http)

Stop and rerun the config service. If all is good output should look like:

DiscoveryClient_CONFIG/10.1.10.235:config:8081: registering service... Tomcat started on port(s): 8081 (http) DiscoveryClient_CONFIG/10.1.10.235:config:8081 - registration status: 204

4. Gateway

Now that we have our configuration and discovery issues resolved we still have a problem with clients accessing all of our applications.

If we leave everything in a distributed system, then we will have to manage complex CORS headers to allow cross-origin requests on clients. We can resolve this by creating a gateway server. This will act as a reverse proxy shuttling requests from clients to our back end servers.

A gateway server is an excellent application in microservice architecture as it allows all responses to originate from a single host. This will eliminate the need for CORS and give us a convenient place to handle common problems like authentication.

4.1. Setup

By now we know the drill. Navigate to //start.spring.io. Set the artifact to “gateway”. Search for “zuul” and add that dependency. Search for “config client” and add that dependency. Search for “eureka discovery” and add that dependency. Lastly, generate that project.

Alternatively, we could create a Spring Boot app with these dependencies:

 org.springframework.cloud spring-cloud-starter-config   org.springframework.cloud spring-cloud-starter-eureka   org.springframework.cloud spring-cloud-starter-zuul 

For reference, we can find the bundle on Maven Central (config-client, eureka-client, zuul).

4.2. Spring Config

Let's add the configuration to the main class:

@SpringBootApplication @EnableZuulProxy @EnableEurekaClient public class GatewayApplication {...}

4.3. Properties

Now we will add two properties files:

bootstrap.properties in src/main/resources:

spring.cloud.config.name=gateway spring.cloud.config.discovery.service-id=config spring.cloud.config.discovery.enabled=true eureka.client.serviceUrl.defaultZone=//localhost:8082/eureka/

gateway.properties in our Git repository

spring.application.name=gateway server.port=8080 eureka.client.region = default eureka.client.registryFetchIntervalSeconds = 5 zuul.routes.book-service.path=/book-service/** zuul.routes.book-service.sensitive-headers=Set-Cookie,Authorization hystrix.command.book-service.execution.isolation.thread.timeoutInMilliseconds=600000 zuul.routes.rating-service.path=/rating-service/** zuul.routes.rating-service.sensitive-headers=Set-Cookie,Authorization hystrix.command.rating-service.execution.isolation.thread.timeoutInMilliseconds=600000 zuul.routes.discovery.path=/discovery/** zuul.routes.discovery.sensitive-headers=Set-Cookie,Authorization zuul.routes.discovery.url=//localhost:8082 hystrix.command.discovery.execution.isolation.thread.timeoutInMilliseconds=600000

The zuul.routes property allows us to define an application to route certain requests based on an ant URL matcher. Our property tells Zuul to route any request that comes in on /book-service/** to an application with the spring.application.name of book-service. Zuul will then lookup the host from discovery server using the application name and forward the request to that server.

Remember to commit the changes in the repository!

4.4. Run

Run the config and discovery applications and wait until the config application has registered with the discovery server. If they are already running, we don't have to restart them. Once that is complete, run the gateway server. The gateway server should start on port 8080 and register itself with the discovery server. The output from the console should contain:

Fetching config from server at: //10.1.10.235:8081/ ... DiscoveryClient_GATEWAY/10.1.10.235:gateway:8080: registering service... DiscoveryClient_GATEWAY/10.1.10.235:gateway:8080 - registration status: 204 Tomcat started on port(s): 8080 (http)

One mistake that is easy to make is to start the server before config server has registered with Eureka. In this case, we'll see a log with this output:

Fetching config from server at: //localhost:8888

This is the default URL and port for a config server and indicates our discovery service did not have an address when the configuration request was made. Just wait a few seconds and try again, once the config server has registered with Eureka, the problem will resolve.

5. Book Service

In microservice architecture, we are free to make as many applications to meet a business objective. Often engineers will divide their services by domain. We will follow this pattern and create a book service to handle all the operations for books in our application.

5.1. Setup

One more time. Navigate to //start.spring.io. Set the artifact to “book-service”. Search for “web” and add that dependency. Search for “config client” and add that dependency. Search for “eureka discovery” and add that dependency. Generate that project.

Alternatively, add these dependencies to a project:

 org.springframework.cloud spring-cloud-starter-config   org.springframework.cloud spring-cloud-starter-eureka   org.springframework.boot spring-boot-starter-web 

For reference, we can find the bundle on Maven Central (config-client, eureka-client, web).

5.2. Spring Config

Let's modify our main class:

@SpringBootApplication @EnableEurekaClient @RestController @RequestMapping("/books") public class BookServiceApplication { public static void main(String[] args) { SpringApplication.run(BookServiceApplication.class, args); } private List bookList = Arrays.asList( new Book(1L, "Baeldung goes to the market", "Tim Schimandle"), new Book(2L, "Baeldung goes to the park", "Slavisa") ); @GetMapping("") public List findAllBooks() { return bookList; } @GetMapping("/{bookId}") public Book findBook(@PathVariable Long bookId) { return bookList.stream().filter(b -> b.getId().equals(bookId)).findFirst().orElse(null); } }

We also added a REST controller and a field set by our properties file to return a value we will set during configuration.

Let's now add the book POJO:

public class Book { private Long id; private String author; private String title; // standard getters and setters }

5.3. Properties

Now we just need to add our two properties files:

bootstrap.properties in src/main/resources:

spring.cloud.config.name=book-service spring.cloud.config.discovery.service-id=config spring.cloud.config.discovery.enabled=true eureka.client.serviceUrl.defaultZone=//localhost:8082/eureka/

book-service.properties in our Git repository:

spring.application.name=book-service server.port=8083 eureka.client.region = default eureka.client.registryFetchIntervalSeconds = 5 eureka.client.serviceUrl.defaultZone=//localhost:8082/eureka/

Let's commit the changes to the repository.

5.4. Run

Once all the other applications have started we can start the book service. The console output should look like:

DiscoveryClient_BOOK-SERVICE/10.1.10.235:book-service:8083: registering service... DiscoveryClient_BOOK-SERVICE/10.1.10.235:book-service:8083 - registration status: 204 Tomcat started on port(s): 8083 (http)

Once it is up we can use our browser to access the endpoint we just created. Navigate to //localhost:8080/book-service/books and we get back a JSON object with two books we added in out controller. Notice that we are not accessing book service directly on port 8083 but we are going through the gateway server.

6. Rating Service

Like our book service, our rating service will be a domain driven service that will handle operations related to ratings.

6.1. Setup

One more time. Navigate to //start.spring.io. Set the artifact to “rating-service”. Search for “web” and add that dependency. Search for “config client” and add that dependency. Search for eureka discovery and add that dependency. Then, generate that project.

Alternatively, add these dependencies to a project:

 org.springframework.cloud spring-cloud-starter-config   org.springframework.cloud spring-cloud-starter-eureka   org.springframework.boot spring-boot-starter-web 

For reference, we can find the bundle on Maven Central (config-client, eureka-client, web).

6.2. Spring Config

Let's modify our main class:

@SpringBootApplication @EnableEurekaClient @RestController @RequestMapping("/ratings") public class RatingServiceApplication { public static void main(String[] args) { SpringApplication.run(RatingServiceApplication.class, args); } private List ratingList = Arrays.asList( new Rating(1L, 1L, 2), new Rating(2L, 1L, 3), new Rating(3L, 2L, 4), new Rating(4L, 2L, 5) ); @GetMapping("") public List findRatingsByBookId(@RequestParam Long bookId)  bookId.equals(0L) ? Collections.EMPTY_LIST : ratingList.stream().filter(r -> r.getBookId().equals(bookId)).collect(Collectors.toList());  @GetMapping("/all") public List findAllRatings() { return ratingList; } }

We also added a REST controller and a field set by our properties file to return a value we will set during configuration.

Let's add the rating POJO:

public class Rating { private Long id; private Long bookId; private int stars; //standard getters and setters }

6.3. Properties

Now we just need to add our two properties files:

bootstrap.properties in src/main/resources:

spring.cloud.config.name=rating-service spring.cloud.config.discovery.service-id=config spring.cloud.config.discovery.enabled=true eureka.client.serviceUrl.defaultZone=//localhost:8082/eureka/

rating-service.properties in our Git repository:

spring.application.name=rating-service server.port=8084 eureka.client.region = default eureka.client.registryFetchIntervalSeconds = 5 eureka.client.serviceUrl.defaultZone=//localhost:8082/eureka/

Let's commit the changes to the repository.

6.4. Run

Once all the other applications have started we can start the rating service. The console output should look like:

DiscoveryClient_RATING-SERVICE/10.1.10.235:rating-service:8083: registering service... DiscoveryClient_RATING-SERVICE/10.1.10.235:rating-service:8083 - registration status: 204 Tomcat started on port(s): 8084 (http)

Once it is up we can use our browser to access the endpoint we just created. Navigate to //localhost:8080/rating-service/ratings/all and we get back JSON containing all our ratings. Notice that we are not accessing the rating service directly on port 8084 but we are going through the gateway server.

7. Conclusion

Jetzt können wir die verschiedenen Teile von Spring Cloud zu einer funktionierenden Microservice-Anwendung verbinden. Dies bildet eine Basis, auf der wir komplexere Anwendungen erstellen können.

Wie immer finden wir diesen Quellcode auf GitHub.