{"id":13694611,"url":"https://github.com/hantsy/spring-microservice-sample","last_synced_at":"2025-04-05T17:07:33.047Z","repository":{"id":24998835,"uuid":"91324844","full_name":"hantsy/spring-microservice-sample","owner":"hantsy","description":"Spring Boot based Mircoservice sample","archived":false,"fork":false,"pushed_at":"2024-01-31T08:09:52.000Z","size":2218,"stargazers_count":306,"open_issues_count":12,"forks_count":118,"subscribers_count":21,"default_branch":"master","last_synced_at":"2025-03-29T16:06:36.855Z","etag":null,"topics":["docker","docker-machine","docker-swarm","docker-toolbox","microservice","ngnix","rest","spring","spring-boot","spring-data-jpa","spring-data-redis","spring-security","spring-session"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hantsy.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2017-05-15T10:21:21.000Z","updated_at":"2025-02-14T22:17:59.000Z","dependencies_parsed_at":"2024-01-14T19:12:10.678Z","dependency_job_id":"56b351cc-2ad3-4a1a-8d0a-c3ab975dcfa0","html_url":"https://github.com/hantsy/spring-microservice-sample","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hantsy%2Fspring-microservice-sample","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hantsy%2Fspring-microservice-sample/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hantsy%2Fspring-microservice-sample/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hantsy%2Fspring-microservice-sample/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hantsy","download_url":"https://codeload.github.com/hantsy/spring-microservice-sample/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247369952,"owners_count":20927928,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["docker","docker-machine","docker-swarm","docker-toolbox","microservice","ngnix","rest","spring","spring-boot","spring-data-jpa","spring-data-redis","spring-security","spring-session"],"created_at":"2024-08-02T17:01:35.681Z","updated_at":"2025-04-05T17:07:33.021Z","avatar_url":"https://github.com/hantsy.png","language":"Java","funding_links":[],"categories":["Java","spring-session"],"sub_categories":[],"readme":"\u003c!-- START doctoc generated TOC please keep comment here to allow auto update --\u003e\n\u003c!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --\u003e\n**Table of Contents**  *generated with [DocToc](https://github.com/thlorenz/doctoc)*\n\n- [Building a Microservices  application with Spring Boot](#building-a-microservices--application-with-spring-boot)\n  - [What is Microservices ?](#what-is-microservices-)\n  - [Migrating to  Microservices  architecture](#migrating-to--microservices--architecture)\n  - [Cooking your first service](#cooking-your-first-service)\n    - [Prerequisites](#prerequisites)\n    - [Setup local development environment](#setup-local-development-environment)\n      - [Docker Toolbox Notes](#docker-toolbox-notes)\n    - [Generate project skeleton](#generate-project-skeleton)\n    - [REST API Overview](#rest-api-overview)\n    - [Create a new Entity](#create-a-new-entity)\n    - [Create `Repository` for Entities](#create-repository-for-entities)\n    - [Create a Domain Service](#create-a-domain-service)\n    - [Expose RESTful APIs](#expose-restful-apis)\n    - [Exception Handling](#exception-handling)\n    - [Miscellaneous](#miscellaneous)\n  - [Secures Microservices](#secures-microservices)\n  - [Running Microservices application](#running-microservices-application)\n    - [Running application via Maven plugin](#running-application-via-maven-plugin)\n    - [Running application via Docker Compose](#running-application-via-docker-compose)\n  - [Testing Microservices](#testing-microservices)\n    - [Testing Single Service](#testing-single-service)\n      - [Testing POJOs](#testing-pojos)\n      - [Testing Repository](#testing-repository)\n      - [Testing PostService](#testing-postservice)\n      - [Testing web facilities](#testing-web-facilities)\n      - [Integration Tests](#integration-tests)\n    - [Testing against External Service](#testing-against-external-service)\n      - [MockRestServiceServer](#mockrestserviceserver)\n      - [WireMock](#wiremock)\n    - [Testing Service-to-Service Communication](#testing-service-to-service-communication)\n      - [Spring Cloud Contracts](#spring-cloud-contracts)\n      - [Pact](#pact)\n  - [Deploying Microservices application](#deploying-microservices-application)\n    - [Publishing Docker Images to Docker Hub](#publishing-docker-images-to-docker-hub)\n    - [Deploying to Docker Swarm](#deploying-to-docker-swarm)\n    - [Deploying to Kubernetes](#deploying-to-kubernetes)\n\n\u003c!-- END doctoc generated TOC please keep comment here to allow auto update --\u003e\n\n# Building a Microservices  application with Spring Boot\n\n**Microservices** is a very hot topic in these years, you can see it everywhere, there are a lots of books, blog entries, conference sessions, training courses etc. are talking about it.\n\n## What is Microservices ?\n\nMicroservices  is not a standard specification, so there is no official definition. Here I listed some well-known explanation from the communities.\n\n[Martin Fowler](https://martinfowler.com/) described it as the following in his article [Microservices](https://martinfowler.com/microservices/):\n\n\u003eIn short, the Microservices  architectural style is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. There is a bare minimum of centralized management of these services, which may be written in different programming languages and use different data storage technologies. \n\nOn the [Wikipedia Microservices page](https://en.wikipedia.org/wiki/Microservices), Microservices was defined as:\n\n\u003eMicroservices is a variant of the service-oriented architecture (SOA) architectural style that structures an application as a collection of loosely coupled services. In a Microservices architecture, services should be fine-grained and the protocols should be lightweight. The benefit of decomposing an application into different smaller services is that it improves modularity and makes the application easier to understand, develop and test. It also parallelizes development by enabling small autonomous teams to develop, deploy and scale their respective services independently.[1] It also allows the architecture of an individual service to emerge through continuous refactoring. Microservices-based architectures enable continuous delivery and deployment.\n\nChris Richardson, the author of *POJOs in Action* and the creator of the original CloudFoundry.com, and also an advocator of Microservices , summarized Microservices  as the following in the home page of [Microservices.io](http://Microservices.io/index.html).\n\n\u003eMicroservices - also known as the Microservices  architecture - is an architectural style that structures an application as a collection of loosely coupled services, which implement business capabilities. The Microservices  architecture enables the continuous delivery/deployment of large, complex applications. It also enables an organization to evolve its technology stack.\n\nThere are some common characteristics can be used to describe a Microservices  based application.\n\n* A Microservices  application should be consisted of a collection of small services. One single service is not Microservices . Every service is fine-grained, and target to perform a small functionality. So Microservices  was described as *fine-grained SOA* or *SOA done right* in some articles. So This is the main difference from traditional monolithic applications.\n\n* Every service should have its own independent life cycle. Every service can be developed and deployed independently, if you are using a CI/CD automation service, every service should be delivered through a standard DevOps pipeline, but not affect others.\n\n* Service-to-service communication is based on light-weight protocols, eg. HTTP based REST APIs for synchronous communication, WebSocket for asynchronous messages, MQTT/AMQP protocol for varied messaging from client or devices(eg. IOT applications).\n\n* The organization or team structures should be changed simultaneously when you are embracing Microservices  architecture.  In the traditional application development, especially your organization follows the waterfall development prototype, your teams are organized by roles, eg architects, database administrators, developers, testers, operators etc. You have to break your traditional organization tree. In the development stage of a Microservices  based application, a small team should be responsible for the whole DevOps lifecycle (design, develop, test, deploy, etc.) of one or more services. \n\nMicroservices componentizes your application into small services(componentized applications), and make it more maintainable and scalable. In this demo application, I will show you building a Microservices application via Spring Boot. \n\n## Migrating to  Microservices  architecture\n\nContrast with Microservices applications, traditional layered enterprise applications were called **monolithic** applications.\n\nIn the past years, I have created some samples to demonstrate different technology stack, such as [REST APIs sample with Spring MVC](https://github.com/hantsy/angularjs-springmvc-sample), [REST APIs sample with Spring Boot](https://github.com/hantsy/angularjs-springmvc-sample-boot).  In these code samples, the backends are monolithic applications and they are based on the same model prototype, **a blog application**.\n\n* A user can log in with an existed account, or sign up a new account.\n* An authenticated user can create a new post.\n* An authenticated user can update his/her posts.\n* An authenticated user who has **ADMIN** role can delete a post directly.\n* All users(who are authenticated or anonymous) can view posts.\n* An authenticated user can add comments to an existed post.\n* ...\n\nNo doubt these monolithic backend applications are easy to develop and deploy, but as time goes by, when the application becomes more complex, the backend will be problematic, you maybe face some barriers which block you to the next stages.\n\n* When applying a change, you have to redeploy the whole backend application even it is just a small fix. The application may be stopped to work for some minutes or some hours.\n* When scaling your applications and deploying multiple copies of the backend applications behinds a load balance server, the transactional consistence will be a new challenge.\n* The database itself will be a huge performance bottleneck when the concurrency of incoming requests are increasing. \n\nMicroservices  architecture addresses these problems, including:\n\n1. Smaller services are easier to maintain in a complex application, when you upgrade one service, you do not need to shut down all services in the production environment.\n2. ACID can not satisfy the scenario of those long run workflows which across several services, although it is still a good option in a single service, but for these long run **transactions**, a stateful *Saga* or workflow solution fills this field. \n3. A service can has its own database, and only responsible for storing data of this service itself.  Traditional complex queries will become a big challenge, in Microservices  architecture, it could need to query multi independent database and aggregate the query results. CQRS, Event Store can save these. Perform commands in standalone services, and execute queries in another service which has marshal view of the data and was synced with messaging from events triggered by other services.\n\nFollow the **Bounded Context** concept of DDD(Domain Driven Design), we break the backend monolithic application into three small services, including:\n\n* An **auth-service** is serving the operations of signin, signup and signout.\n* A **user-service** is responsible for user management.\n* A **post-service** exposes APIs for a simple CMS, including posts and comments.\n* An **API Gateway** which is just responsible for routing the incoming requests to downstream services.\n* The databases are also aligned to Microservices  architecture, and **user-service** and **post-service** have their own databases, a **Redis** is used for sharing session between services, and to simplify the security.\n\n![Microservices ](./microservice.png)\n\nAs mentioned, if there is a [legacy application](https://github.com/hantsy/angularjs-springmvc-sample) planned to migrate to Microservices  architecture, you can follow the following steps to extract some domain into a standalone service.\n\n1. Find the domains which are easiest to separate from the main application, eg, posts and comments in our application.\n2. Use an identifier object in the entity links instead of the hard relations of entities outside of this domain. eg. use a `Username` which stands for a unique username of a `User` entity, and erase the direct connection to `User` entity.\n3. Move the related data to a standalone database, and connect to this new database in your service.\n\nWhen I start a new project, should I embrace Microservices  architecture right now?\n\nAlthough we are talking about Microservices  in this post, I still suggest you start building your application in a monolithic architecture if you know little about the complexity of Microservices , it could be consisted of a RESTful backend and an SPA based frontend UI. In the initial development stage, either monolithic architecture or Microservices , you have to spend lots of time on clarifying the problem domains, defining the bounded context etc. Starting a monolithic application is still valuable when you are ready for migrating to Microservices  architecture.\n\n## Cooking your first service\n\nThis sample application is built on the newest Spring technology stack, including Spring Boot, Spring Data, Spring Security, etc. \n\n* Every small service is a Spring Boot application. Every service will be packaged as a **jar** file and use the embedded Tomcat as target runtime to serve the services.\n* Every small service owns its database, eg. we use MySQL as the backing database for **auth-service**, and PostgreSQL for the **post-service**.\n* Spring Data is used for simplifying data operations.\n* Spring Session provides a simple strategy to generate and validate header based authentication token via sharing sessions in a backing session repository, in this sample we use Redis as session storage.\n* Spring Security is responsible for protecting RESTful APIs.\n\nFollow the 12 factors application guide, I suggest you use Docker in both development and production environment to make sure the same code base works well in different environments.\n\nIn this section, we will build our first service, **post-service**, which is designated to exposes REST APIs to clients.\n\n### Prerequisites\n\nI assume you have some experience of Spring, and know well about the [REST convention](https://en.wikipedia.org/wiki/Representational_state_transfer), especially the [CHAPTER 5: Representational State Transfer (REST)](https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm) from Roy Fielding's dissertation: [Architectural Styles and\nthe Design of Network-based Software Architectures](https://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm). \n\nAnd you have also installed the following software.\n\n* JDK 8, eg. [Oracle Java 8 SDK](https://java.oracle.com) \n* The latest [Apache Maven](https://maven.apache.org)\n* Optional [Gradle](http://www.gradle.org) if you prefer Gradle as build tools\n* Your favorite IDE, including :\n  * [NetBeans IDE](http://www.netbeans.org)\n  * [Eclipse IDE](http://www.eclipse.org) (or  Eclipse based IDE,  Spring ToolSuite is highly recommended) \n  * [Intellij IDEA](http://www.jetbrains.com)\n\n\n### Setup local development environment\n\nMake sure you have installed the latest Docker, Docker Compose and Docker Machine, more info please refer to the installation guide from [Docker official website](https://www.docker.com).\n\n\u003eNOTE: Under Windows system, you can install Docker Desktop for Windows to simplify the installation.\n\nDocker Compose allows you start up the dependent infrastructural services(such as Database etc) via a single `docker-compose` command.\n\n```\ndocker-compose up\n```\n\nWe will use MySQL, PostgreSQL and Redis in this demo, the following is a sample *docker-compose.yml* file.\n\n```yaml\nversion: '3.3' # specify docker-compose version\n\nservices:    \n  userdb:\n    container_name: userdb\n    image: mysql\n    ports:\n      - \"3306:3306\"\n    environment:\n      MYSQL_ROOT_PASSWORD: mysecret\n      MYSQL_USER: user\n      MYSQL_PASSWORD: password\n      MYSQL_DATABASE: userdb\n    volumes:\n      - ./data/userdb:/var/lib/mysql\n      \n  postdb:\n    container_name:  postdb\n    image: postgres\n    ports:\n      - \"5432:5432\"\n    restart: always\n    environment:\n      POSTGRES_PASSWORD: password\n      POSTGRES_DB: postdb\n    volumes:\n      - ./data/postdb:/var/lib/postgresql     \n      \n\n  redis:\n    container_name: redis\n    image: redis\n    ports:\n      - \"6379:6379\"\n```\n\n\n#### Docker Toolbox Notes\n\nIf you are using the legacy Docker Toolbox, create a new machine for this project.\n\n```\n$ docker-machine create -d virtualbox --engine-registry-mirror https://docker.mirrors.ustc.edu.cn springms\n```\n\n\u003eNOTE: The `--engine-registry-mirror https://docker.mirrors.ustc.edu.cn` will add a docker registry mirror setting in docker-machine specific *config.json*. For most of Chinese users, using a local mirror will speed up the Docker images downloading.\n\nThen switch to the new created machine **springms**, and set the environment variables.\n\n```\neval \"$(docker-machine env springms)\"\n```\n\nForward the virtualbox ports to your local system, thus you can access the servers via `localhost` instead of the docker machine IP address.\n\n```d\n VBoxManage modifyvm \"springms\" --natpf1 \"tcp-port3306,tcp,,3306,,3306\"\n VBoxManage modifyvm \"springms\" --natpf1 \"tcp-port5432,tcp,,5432,,5432\"\n VBoxManage modifyvm \"springms\" --natpf1 \"tcp-port5672,tcp,,5672,,5672\"\n VBoxManage modifyvm \"springms\" --natpf1 \"tcp-port15672,tcp,,15672,,15672\"\n VBoxManage modifyvm \"springms\" --natpf1 \"tcp-port6379,tcp,,6379,,6379\"\n VBoxManage modifyvm \"springms\" --natpf1 \"tcp-port27017,tcp,,27017,,27017\"\n```\n\nThen run the dependent servers via `docker-compose` command line.\n\n### Generate project skeleton\n\nWith [Spring Initializr](https://start.spring.io), you can get a Spring Boot based project skeleton in seconds. \n\nOpen your browser, go to [Spring Initializr](https://start.spring.io) page, fill the following essential fields for a project.\n\n1. Choose **Java** as programming language.\n2. Select the latest version of Spring Boot, **2.0.0.RELEASE** is the latest milestone at the moment when I wrote this post.\n3. Search and select the required facilities will be used in your project, such as **Web**, **Data JPA**, **Data Redis**, **Security**, **Session**, **Lombok** etc.\n4. Set project name(maven artifact id) to **post-service**. \n\nClick **Generate Project** button or press **ALT+ENTER** keys to generate the project skeleton for downloading in your browser.\n\nAfter downloading the generated archive, extract the files into your local disk and import it into your favorite IDE.\n\n### REST API Overview\n\nFollowing the REST convention and HTTP protocol specification, the REST APIs of post-service are designed as the following table.\n\n| Uri                    | Http Method | Request                                  | Response                                 | Description                              |\n| ---------------------- | ----------- | ---------------------------------------- | ---------------------------------------- | ---------------------------------------- |\n| /posts                 | GET         |                                          | 200, [{'id':1, 'title'},{}]              | Get all posts                            |\n| /posts                 | POST        | {'id':1, 'title':'test title','content':'test content'} | 201                                      | Create a new post                        |\n| /posts/{postSlug}          | GET         |                                          | 200, {'id':1, 'title'}                   | Get a post by postSlug                       |\n| /posts/{postSlug}          | PUT         | {'title':'test title','content':'test content'} | 204                                      | Update a post                            |\n| /posts/{postSlug}          | DELETE      |                                          | 204                                      | Delete a post by postSlug                    |\n| /posts/{postSlug}/comments | GET         |                                          | 200, [{'id':1, 'content':'comment content'},{}] | Get all comments of the certain post     |\n| /posts/{postSlug}/comments | POST        | {'content':'test content'}               | 201                                      | Create a new comment of the certain post |\n\n\n### Create a new Entity\n\nA domain entity is a persistent object in DDD concept, JPA `@Entity` a is a good match.\n\nCreate our first entity `Post`.\n\n```java\n@Data\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\n@Entity\nclass Post extends AuditableEntity {\n\n    @JsonView(View.Summary.class)\n    @NotEmpty\n    private String title;\n    \n    @NotEmpty\n    private String postSlug;\n\n    @JsonView(View.Public.class)\n    @NotEmpty\n    private String content;\n\n    @Enumerated(EnumType.STRING)\n    @Builder.Default\n    @JsonView(View.Summary.class)\n    private Status status = Status.DRAFT;\n\n    static enum Status {\n        DRAFT,\n        PUBLISHED\n    }\n      \n    @PrePersist\n    public void slugify(){\n        this.postSlug = new Slugify().slugify(this.title);\n    }\n\n}\n```\n\n`@Data`, `@Builder`, `@NoArgsConstructor` and `@AllArgsConstructor` are from project **Lombok**, which provides some helper annotations to make your source codes clean. With `@Data`, you can remove the tedious setters, getters of all fields, and the generic `equals`, `hashCode`, `toString` methods. `@Builder` will generate an inner builder class. `@NoArgsConstructor` will create a none-argument constructor, `@AllArgsConstructor` will take all fields as constructor arguments.\n\nThese annotations will be handled by JDK **Annotation Processing Tooling**, and generate code fragment in class files at compile time. \n\n`@Entity` indicates `Post` is a standard JPA Entity.\n\n`@PrePersist` is a JPA lifecycle hook. The `@PrePersist` annotated methods will be executed before the entity is persisted.  We use post postSlug as the unique identifier of a `Post`, and we use `slugify()` method to generate the post postSlug automatically.\n\n`AuditableEntity` is a helper class to centralize some common fields of a JPA entity in one place.\n\n```java\n@Data\n@MappedSuperclass\n@EntityListeners(value = AuditingEntityListener.class)\npublic abstract class AuditableEntity extends PersistableEntity {\n\n    public AuditableEntity() {\n    }\n\n    @CreatedDate\n    @JsonView(View.Summary.class)\n    protected LocalDateTime createdDate;\n\n    @Embedded\n    @AttributeOverrides(value = {\n        @AttributeOverride(name = \"username\", column = @Column(name = \"author\"))\n    })\n    @CreatedBy\n    @JsonView(View.Summary.class)\n    protected Username author;\n\n}\n```\n\n`@CreatedDate` and `@CreatedBy` will fill in the creation date timestamp and the current user if the data auditing feature is enabled. \n\nUse a standalone `@Configuration` bean to configure Spring Data JPA auditing.\n\n```java\n@Configuration\n@EnableJpaAuditing(auditorAwareRef = \"auditorAware\")\n@Slf4j\npublic class DataJpaConfig {\n\n    @Bean\n    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)\n    public AuditorAware\u003cUsername\u003e auditorAware() {\n\n        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();\n\n        log.debug(\"current authentication:\" + authentication);\n\n        if (authentication == null || !authentication.isAuthenticated()) {\n            return () -\u003e Optional.\u003cUsername\u003eempty();\n        }\n\n        return () -\u003e Optional.of(\n            Username.builder()\n                .username(((UserDetails) authentication.getPrincipal()).getUsername())\n                .build()\n        );\n\n    }\n}\n```\n\n`AuditorAware` bean is required when you want to set auditor automatically. The population work is done by JPA `@EntityListener`, note there is a `@EntityListeners(value = AuditingEntityListener.class)` already added on the `AuditableEntity` class.\n\nHave a look at the base `PersistableEntity`, it just defines the identity field of a JPA entity.\n\n```java\n@Data\n@MappedSuperclass\npublic abstract class PersistableEntity implements Serializable {\n    \n    @Id\n    @GeneratedValue(strategy = GenerationType.AUTO)\n    @JsonView(View.Summary.class)\n    protected Long id;\n\n    public PersistableEntity() {\n    }\n    \n}\n```\n\nSimilarly, create an another entity `Comment`.\n\n```java\n@Data\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\n@Entity\npublic class Comment extends AuditableEntity {\n\n\n    @NotEmpty\n    @Size(min = 10)\n    private String content;\n\n\n    @Embedded\n    @AttributeOverrides(\n        value = {\n            @AttributeOverride(name = \"postSlug\", column = @Column(name = \"post_slug\"))\n        }\n    )\n    @JsonIgnore\n    private Slug post;\n\n\n}\n```\n\n\u003eNOTE: we do not user a JPA `@OneToMany` or `@ManyToOne` to connect two entities, but use a simple Post `Slug` identifier object instead. If one day this service becomes heavy, we could split comments into another standalone service.\n\n### Create `Repository` for Entities\n\nIn DDD, a **Repository** is responsible for retrieving entities from or saving back to a **Repository**.  Spring Data `Repository` interface and Spring Data JPA specific `JpaRepository` interface are a good match with **Repository** concept in DDD.\n\nCreate a `Repository` for the `Post`  Entity.\n\n```java\npublic interface PostRepository extends JpaRepository\u003cPost, Long\u003e, JpaSpecificationExecutor\u003cPost\u003e {\n\n    Optional\u003cPost\u003e findBySlug(String postSlug);\n    \n}\n```\n\n### Create a Domain Service\n\n```java\n@Service\n@Transactional\npublic class PostService {\n\n    @Inject\n    private PostRepository postRepository;\n    \n\n    public Post createPost(PostForm form) {\n        Post _post = Post.builder()\n            .title(form.getTitle())\n            .content(form.getContent())\n            .build();\n        \n        Post saved = this.postRepository.save(_post);\n        \n        return saved;\n    }\n\n    public Post updatePost(String postSlug, PostForm form) {\n        Post _post = this.postRepository.findBySlug(postSlug).orElseThrow(\n            ()-\u003e {\n                return new PostNotFoundException(postSlug);\n            }\n        );\n        \n        _post.setTitle(form.getTitle());\n        _post.setContent(form.getContent());\n        \n       Post saved =  this.postRepository.save(_post);\n       \n       return saved;\n    }\n\n    public void deletePost(String postSlug) {\n        this.postRepository.delete(this.postRepository.findBySlug(postSlug).orElseThrow(\n            () -\u003e {\n                return new PostNotFoundException(postSlug);\n            }\n        ));\n    }\n\n}\n```\n\nIn the `PostService`, the main purpose is treating with exceptions when creating or update a post. In a real world application, you could handle domain events in a domain service, eg. Post is published, etc.\n\n### Expose RESTful APIs\n\nLet's expose RESTful APIs for `Post` via `PostController`.\n\n```java\n@RestController\n@RequestMapping(\"/posts\")\n@Slf4j\npublic class PostController {\n\n    private PostService postService;\n\n    private PostRepository postRepository;\n\n    private CommentRepository commentRepository;\n\n    public PostController(PostService postService, PostRepository postRepository, CommentRepository commentRepository) {\n        this.postService = postService;\n        this.postRepository = postRepository;\n        this.commentRepository = commentRepository;\n    }\n\n    @GetMapping()\n    @JsonView(View.Summary.class)\n    public ResponseEntity\u003cPage\u003cPost\u003e\u003e getAllPosts(\n        @RequestParam(value = \"q\", required = false) String keyword, //\n        @RequestParam(value = \"status\", required = false) Post.Status status, //\n        @PageableDefault(page = 0, size = 10, sort = \"createdDate\", direction = Direction.DESC) Pageable page) {\n\n        log.debug(\"get all posts of q@\" + keyword + \", status @\" + status + \", page@\" + page);\n\n        Page\u003cPost\u003e posts = this.postRepository.findAll(PostSpecifications.filterByKeywordAndStatus(keyword, status), page);\n\n        return ok(posts);\n    }\n\n    @GetMapping(value = \"/{postSlug}\")\n    @JsonView(View.Public.class)\n    public ResponseEntity\u003cPost\u003e getPost(@PathVariable(\"postSlug\") String postSlug) {\n\n        log.debug(\"get postsinfo by postSlug @\" + postSlug);\n\n        Post post = this.postRepository.findBySlug(postSlug).orElseThrow(\n            () -\u003e {\n                return new PostNotFoundException(postSlug);\n            }\n        );\n\n        log.debug(\"get post @\" + post);\n\n        return ok(post);\n    }\n\n    @PostMapping()\n    public ResponseEntity\u003cVoid\u003e createPost(@RequestBody @Valid PostForm post, HttpServletRequest request) {\n\n        log.debug(\"create a new post@\" + post);\n\n        Post saved = this.postService.createPost(post);\n\n        log.debug(\"saved post id is @\" + saved.getId());\n        URI createdUri = ServletUriComponentsBuilder\n            .fromContextPath(request)\n            .path(\"/posts/{postSlug}\")\n            .buildAndExpand(saved.getSlug())\n            .toUri();\n\n        return created(createdUri).build();\n    }\n\n    @PutMapping(value = \"/{postSlug}\")\n    public ResponseEntity\u003cVoid\u003e updatePost(@PathVariable(\"postSlug\") String postSlug, @RequestBody @Valid PostForm form) {\n\n        log.debug(\"update post by id @\" + postSlug + \", form content@\" + form);\n\n        this.postService.updatePost(postSlug, form);\n\n        return noContent().build();\n    }\n\n    @DeleteMapping(value = \"/{postSlug}\")\n    public ResponseEntity\u003cVoid\u003e deletePostById(@PathVariable(\"postSlug\") String postSlug) {\n\n        log.debug(\"delete post by id @\" + postSlug);\n\n        this.postService.deletePost(postSlug);\n\n        return noContent().build();\n    }\n\n    @GetMapping(value = \"/{postSlug}/comments\")\n    public ResponseEntity\u003cPage\u003cComment\u003e\u003e getCommentsOfPost(\n        @PathVariable(\"postSlug\") String postSlug,\n        @PageableDefault(page = 0, size = 10, sort = \"createdDate\", direction = Direction.DESC) Pageable page) {\n\n        log.debug(\"get comments of post@\" + postSlug + \", page@\" + page);\n\n        Page\u003cComment\u003e commentsOfPost = this.commentRepository.findByPost(new Slug(postSlug), page);\n\n        log.debug(\"get post comment size @\" + commentsOfPost.getTotalElements());\n\n        return ok(commentsOfPost);\n    }\n\n    @PostMapping(value = \"/{postSlug}/comments\")\n    public ResponseEntity\u003cVoid\u003e createComment(\n        @PathVariable(\"postSlug\") @NotNull String postSlug, @RequestBody CommentForm comment, HttpServletRequest request) {\n\n        log.debug(\"new comment of post@\" + postSlug + \", comment\" + comment);\n\n        Comment _comment = Comment.builder()\n            .post(new Slug(postSlug))\n            .content(comment.getContent())\n            .build();\n\n        Comment saved = this.commentRepository.save(_comment);\n\n        log.debug(\"saved comment @\" + saved.getId());\n\n        URI location = ServletUriComponentsBuilder\n            .fromContextPath(request)\n            .path(\"/posts/{postSlug}/comments/{id}\")\n            .buildAndExpand(postSlug, saved.getId())\n            .toUri();\n\n         return created(location).build();\n    }\n\n}\n```\n\nIn the above codes, \n\n* `getAllPosts` method accepts a **q** (keyword) and a **status** (post status) and a  `Pageable` as query parameters, it returns a `Page\u003cPost\u003e` result. \n* The `postRepository.findAll` method accepts a `Specification` object. `Specification` is a wrapper class of JPA 2.0 criteria APIs, which provides effective type safe query condition building. \n\n\n```java\npublic class PostSpecifications {\n\n    private PostSpecifications() {\n    }\n\n    public static Specification\u003cPost\u003e filterByKeywordAndStatus(\n        final String keyword,//\n        final Post.Status status) {\n        return (Root\u003cPost\u003e root, CriteriaQuery\u003c?\u003e query, CriteriaBuilder cb) -\u003e {\n            List\u003cPredicate\u003e predicates = new ArrayList\u003c\u003e();\n            if (StringUtils.hasText(keyword)) {\n                predicates.add(\n                    cb.or(\n                        cb.like(root.get(Post_.title), \"%\" + keyword + \"%\"),\n                        cb.like(root.get(Post_.content), \"%\" + keyword + \"%\")\n                    )\n                );\n            }\n\n            if (status != null) {\n                predicates.add(cb.equal(root.get(Post_.status), status));\n            }\n\n            return cb.and(predicates.toArray(new Predicate[predicates.size()]));\n        };\n    }\n\n}\n```\n\nAccording to the REST convention and HTTP protocol, a HTTP POST Method is used to create a new resource, it can return a 201 HTTP status code with the new created resource URI as HTTP header **Location**. And for update and delete operations on resource, return a  204 HTTP status. In the above codes, we apply these simple rules.\n\n### Exception Handling\n\nAs mentioned above, in our `PostService`, I have added some extra steps to check the existence of a post by id in the `updatePost` and `deletePost` methods. If it is not found throw a `PostNotFoundException`.\n\n```java\npublic class PostNotFoundException extends RuntimeException {\n\n    private String postSlug;\n\n    public PostNotFoundException(String postSlug) {\n        super(\"post:\" + postSlug + \" was not found\");\n        this.postSlug = postSlug;\n    }\n\n    public String getSlug() {\n        return postSlug;\n    }\n    \n}\n```\n\nAnd we will handle this exception in a common class annotated with `@RestControllerAdvice`. When a `PostNotFoundException` is caught, `notFound` method will handle it convert the exception to a friendly message body and return a HTTP 404 status code to the client.\n\n```java\n@RestControllerAdvice\npublic class PostExceptionHandler {\n\n    @ExceptionHandler(PostNotFoundException.class)\n    public ResponseEntity notFound(PostNotFoundException ex, WebRequest req) {\n        Map\u003cString, String\u003e errors = new HashMap\u003c\u003e();\n        errors.put(\"entity\", \"POST\");\n        errors.put(\"id\", \"\" + ex.getSlug());\n        errors.put(\"code\", \"not_found\");\n        errors.put(\"message\", ex.getMessage());\n\n        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errors);\n    }\n\n}\n```\n\n### Miscellaneous\n\nIn a real world application, when you fetch post list, you maybe do not want to show all fields of the post. It is easy to control the representation view sent to client by customizing Jackson `JsonView`.\n\n```java\npublic final class View {\n\n    interface Summary {\n    }\n\n    interface Public extends Summary {\n    }\n}\n```\n\nIn the `Post` class, add the following annotations to its fields.\n\n```java\nclass Post extends AuditableEntity {\n\n    @JsonView(View.Summary.class)\n    private String title;\n    \n\n    @JsonView(View.Public.class)\n    private String content;\n\n    @JsonView(View.Summary.class)\n    private Status status = Status.DRAFT;\n\n}\n```\n\nIn the `PostController`, add a `@JsonView` annotation.\n\n```java\n@JsonView(View.Summary.class)\npublic ResponseEntity\u003cPage\u003cPost\u003e\u003e getAllPosts()\n```\n\nThus only the `Summary` labeled fields will be included in the result of `getAllPosts`.\n\nAnother small issue you could have found is the `Page` object serialized result looks a little tedious, too much unused fields from `Pageable` are included in the json result.\n\n```json\n{\n  \"content\" : [ {\n    \"title\" : \"test post 2\",\n    \"postSlug\" : \"test-post-2\",\n    \"status\" : \"DRAFT\",\n    \"id\" : 2,\n    \"createdDate\" : \"2017-05-25T06:53:30\",\n    \"author\" : {\n      \"username\" : \"user\"\n    }\n  }, {\n    \"title\" : \"test post\",\n    \"postSlug\" : \"test-post\",\n    \"status\" : \"DRAFT\",\n    \"id\" : 1,\n    \"createdDate\" : \"2017-05-25T06:52:45\",\n    \"author\" : {\n      \"username\" : \"user\"\n    }\n  } ],\n  \"pageable\" : {\n    \"sort\" : {\n      \"sorted\" : true,\n      \"unsorted\" : false\n    },\n    \"pageSize\" : 10,\n    \"pageNumber\" : 0,\n    \"offset\" : 0,\n    \"paged\" : true,\n    \"unpaged\" : false\n  },\n  \"last\" : true,\n  \"totalElements\" : 2,\n  \"totalPages\" : 1,\n  \"sort\" : {\n    \"sorted\" : true,\n    \"unsorted\" : false\n  },\n  \"numberOfElements\" : 2,\n  \"first\" : true,\n  \"size\" : 10,\n  \"number\" : 0\n}\n```\n\nCreate a `@JsonComponent` bean to customize the serialized json result.\n\n```java\n@JsonComponent\npublic class PageJsonSerializer extends JsonSerializer\u003cPageImpl\u003e {\n\n    @Override\n    public void serialize(PageImpl value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {\n        gen.writeStartObject();\n        gen.writeNumberField(\"number\", value.getNumber());\n        gen.writeNumberField(\"numberOfElements\", value.getNumberOfElements());\n        gen.writeNumberField(\"totalElements\", value.getTotalElements());\n        gen.writeNumberField(\"totalPages\", value.getTotalPages());\n        gen.writeNumberField(\"size\", value.getSize());\n        gen.writeFieldName(\"content\");\n        serializers.defaultSerializeValue(value.getContent(), gen);\n        gen.writeEndObject();\n    }\n\n}\n```\n\nWhen this bean is activated, the result cloud look like the following:\n\n```json\n{\n  \"content\" : [ {\n    \"title\" : \"test post 2\",\n    \"postSlug\" : \"test-post-2\",\n    \"status\" : \"DRAFT\",\n    \"id\" : 2,\n    \"createdDate\" : \"2017-05-25T06:53:30\",\n    \"author\" : {\n      \"username\" : \"user\"\n    }\n  }, {\n    \"title\" : \"test post\",\n    \"postSlug\" : \"test-post\",\n    \"status\" : \"DRAFT\",\n    \"id\" : 1,\n    \"createdDate\" : \"2017-05-25T06:52:45\",\n    \"author\" : {\n      \"username\" : \"user\"\n    }\n  } ],\n  \"numberOfElements\" : 2,\n  \"totalElements\" : 2,\n  \"totalPages\" : 1,\n  \"size\" : 10,\n  \"number\" : 0\n}\n```\n\nThe details of **auth-service** and **user-service**, please check the [source codes](https://github.com/hantsy/spring-Microservices -sample) and explore them yourself.\n\n## Secures Microservices \n\nLet's have a look at how a user get authentication in this demo.\n\n1. A user try to get authentication from **auth-service** using usename and password.\n2. If it is a valid user and it is authenticated successfully, the response header will include a **X-AUTH-TOKEN** header.\n3. Extract the value of  **X-AUTH-TOKEN** header, and add **X-AUTH-TOKEN** header into the new request to get access permission of the protected resource, such as APIs in **post-service**.\n\nWe use Spring Session and Redis to archive this purpose.\n\nIn all services, we add the following codes to resolve Session by HTTP header instead of Cookie.\n\n```java\n@Configuration\npublic class RedisSessionConfig {\n\n    @Bean\n    public HttpSessionIdResolver httpSessionStrategy() {\n        return HeaderHttpSessionIdResolver.xAuthToken();\n    }\n\n}\n```\n\nAnd add the follow configuration in the *application.yml* to tell Spring to use Redis as session store.\n\n```yml\nspring:\n  session: \n    store-type: redis\n```\n\nIn **auth-service**, use a controller to serve user authentication.\n\n```java\n@RequestMapping(value = \"/auth\")\n@RestController\npublic class AuthenticationController {\n\n\t@PostMapping(value = \"/signin\")\n    public AuthenticationResult signin(\n        @RequestBody @Valid AuthenticationRequest authenticationRequest,\n        HttpServletRequest request) {\n        \n        if (log.isDebugEnabled()) {\n            log.debug(\"signin form  data@\" + authenticationRequest);\n        }\n        \n        return this.handleAuthentication(\n            authenticationRequest.getUsername(),\n            authenticationRequest.getPassword(),\n            request);\n    }\n    \n    private AuthenticationResult handleAuthentication(\n        String username,\n        String password,\n        HttpServletRequest request) {\n        \n        final UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(\n            username,\n            password\n        );\n        \n        final Authentication authentication = this.authenticationManager\n            .authenticate(token);\n        \n        SecurityContextHolder.getContext().setAuthentication(authentication);\n        \n        final HttpSession session = request.getSession(true);\n        \n        session.setAttribute(\n            HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,\n            SecurityContextHolder.getContext());\n        \n        return AuthenticationResult.builder()\n            .name(authentication.getName())\n            .roles(authentication.getAuthorities().stream().map(r -\u003e r.getAuthority()).collect(Collectors.toList()))\n            .token(session.getId())\n            .build();\n    }\n\t\n\t...\n```\n\nWhen you are authenticated, the `/auth/signin` endpoint will return userinfo and token(session id) in the result.\n\nTo protect the resource APIs, just add a `SecurityConfig`. The following is a configuration for post-service. All **GET** methods are permitted, and when **DELETE** a post, you should have a **ADMIN** role.\n\n```java\n@Configuration\n@Slf4j\npublic class SecurityConfig {\n    \n    @Bean\n    public WebSecurityConfigurerAdapter securityConfigBean(){\n        \n        return new  WebSecurityConfigurerAdapter() {\n\n            @Override\n            protected void configure(HttpSecurity http) throws Exception {\n                // We need this to prevent the browser from popping up a dialog on a 401\n                http\n                    .httpBasic()\n                    .and()\n                        .authorizeRequests()\n                        .antMatchers(HttpMethod.GET, \"/posts/**\").permitAll()\n                        .antMatchers(HttpMethod.DELETE, \"/posts/**\").hasRole(\"ADMIN\")\n                        .anyRequest().authenticated()\n                    .and()\n                        .csrf().disable();\n            }\n        };   \n    }\n}\n```\n\nLet's try to run the demo in local system.\n\n## Running Microservices application\n\nIn your local development environment, it is easy to run the services one by one via Spring Boot maven plugin or build and run them in local Docker container via a predefined *docker compose* file.\n\n### Running application via Maven plugin\n\nMake sure the dependent servers are running by executing `docker-compose up`. \n\nEnter the root folder of every service, execute the following command to start up them one by one.\n\n```\nmvn spring-boot:run // run in user-service, auth-service, post-service\n```\n\nThe following endpoints will be provided.\n\n| Service      | Url                                      | Description                              |\n| ------------ | ---------------------------------------- | ---------------------------------------- |\n| auth-service | http://localhost:8000/user,http://localhost:8000/auth | Authentication APIs(signin, signup, signout), user info |\n| user-service | http://localhost:8001/users              | User management APIs                     |\n| post-service | http://localhost:8002/posts              | Post and comment APIs                    |\n\n\nFollow the authentication flow to have a try.\n\nWhen all service are running successfully, firstly try to get authentication.\n\n```\ncurl -v  http://localhost:8000/user -u user:test123\n* timeout on name lookup is not supported\n*   Trying ::1...\n* TCP_NODELAY set\n* Connected to localhost (::1) port 8000 (#0)\n* Server auth using Basic with user 'user'\n\u003e GET /user HTTP/1.1\n\u003e Host: localhost:8000\n\u003e Authorization: Basic dXNlcjp0ZXN0MTIz\n\u003e User-Agent: curl/7.54.0\n\u003e Accept: */*\n\u003e\n\u003c HTTP/1.1 200\n\u003c X-Content-Type-Options: nosniff\n\u003c X-XSS-Protection: 1; mode=block\n\u003c Cache-Control: no-cache, no-store, max-age=0, must-revalidate\n\u003c Pragma: no-cache\n\u003c Expires: 0\n\u003c X-Frame-Options: DENY\n\u003c X-Auth-Token: 49090ba7-e641-45e3-935b-894a43b85f62\n\u003c Content-Type: application/json;charset=UTF-8\n\u003c Transfer-Encoding: chunked\n\u003c Date: Mon, 15 May 2017 09:29:14 GMT\n\u003c\n{\"name\":\"user\",\"roles\":[\"USER\"]}* Connection #0 to host localhost left intact\n```\n\nYou will see a `X-Auth-Token` header in the response.\n\nPut this header into a new request when you want to access the protected resources in another resource server.\n\n```\ncurl -v  http://localhost:8001/user -H \"x-auth-token: 49090ba7-e641-45e3-935b-894a43b85f62\"\n```\n\nTry to add some posts data:\n\n```\n\u003ecurl -v  http://localhost:8002/posts \n-H \"x-auth-token:  49090ba7-e641-45e3-935b-894a43b85f62\" \n-H \"Accept: application/json\" \n-H \"Content-Type: application/json;charset=UTF-8\" \n-X POST \n-d \"{\\\"title\\\": \\\"test post\\\", \\\"content\\\":\\\"test content of post\\\"}\"\n```\n\nYou will see the result. It returns 201 status, and set `Location` header to the new created `Post`.\n\n```\nNote: Unnecessary use of -X or --request, POST is already inferred.\n* timeout on name lookup is not supported\n*   Trying ::1...\n* TCP_NODELAY set\n* Connected to localhost (::1) port 8002 (#0)\n\u003e POST /posts HTTP/1.1\n\u003e Host: localhost:8002\n\u003e User-Agent: curl/7.54.0\n\u003e x-auth-token:  49090ba7-e641-45e3-935b-894a43b85f62\n\u003e Accept: application/json\n\u003e Content-Type: application/json;charset=UTF-8\n\u003e Content-Length: 56\n\u003e\n* upload completely sent off: 56 out of 56 bytes\n\u003c HTTP/1.1 201\n\u003c X-Content-Type-Options: nosniff\n\u003c X-XSS-Protection: 1; mode=block\n\u003c Cache-Control: no-cache, no-store, max-age=0, must-revalidate\n\u003c Pragma: no-cache\n\u003c Expires: 0\n\u003c X-Frame-Options: DENY\n\u003c Location: http://localhost:8002/posts/4\n\u003c Content-Length: 0\n\u003c Date: Thu, 18 May 2017 06:54:40 GMT\n```\n\nFetch the new created post.\n\n```\ncurl -v  http://localhost:8002/posts/4 -H \"Accept: application/json\"\n* timeout on name lookup is not supported\n*   Trying ::1...\n* TCP_NODELAY set\n* Connected to localhost (::1) port 8002 (#0)\n\u003e GET /posts/4 HTTP/1.1\n\u003e Host: localhost:8002\n\u003e User-Agent: curl/7.54.0\n\u003e Accept: application/json\n\u003e\n\u003c HTTP/1.1 200\n\u003c X-Content-Type-Options: nosniff\n\u003c X-XSS-Protection: 1; mode=block\n\u003c Cache-Control: no-cache, no-store, max-age=0, must-revalidate\n\u003c Pragma: no-cache\n\u003c Expires: 0\n\u003c X-Frame-Options: DENY\n\u003c Content-Type: application/json;charset=UTF-8\n\u003c Transfer-Encoding: chunked\n\u003c Date: Thu, 18 May 2017 06:59:42 GMT\n\u003c\n{\"id\":4,\"title\":\"test post\",\"content\":\"test content of post\",\"status\":\"DRAFT\",\"author\":null,\"createdDate\":null}*\n```\n\n### Running application via Docker Compose\n\nFirstly build all services into Docker images.\n\nPrepare a *Dockfile* for every service. For example,  create a Dockerfile in the root folder of *post-service* project.\n\n```dockerfile\nFROM frolvlad/alpine-oraclejdk8:slim\nVOLUME /tmp\nADD ./target/post-service-0.0.1-SNAPSHOT.jar app.jar\nRUN sh -c 'touch /app.jar'\nENV JAVA_OPTS=\"\"\nENTRYPOINT [ \"sh\", \"-c\", \"java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar\" ]\n```\n\nThe Dockerfile in **auth-service** and **user-service** are similar, just replaced the maven build target **jar**  file.\n\n```dockerfile\nFROM frolvlad/alpine-oraclejdk8:slim\nVOLUME /tmp\nADD ./target/auth-service-0.0.1-SNAPSHOT.jar app.jar\nRUN sh -c 'touch /app.jar'\nENV JAVA_OPTS=\"\"\nENTRYPOINT [ \"sh\", \"-c\", \"java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar\" ]\n```\nCreate a *Dockerfile* for ngnix. We will use ngnix as a reverse proxy to unite the entry of the application.\n\n```dockerfile\n# Set nginx base image\nFROM nginx \n\n#RUN mkdir /etc/nginx/ssl  \n#COPY ssl /etc/nginx/ssl \n\n# Copy custom configuration file from the current directory\nCOPY nginx.conf /etc/nginx/nginx.conf\n  \n#COPY www /usr/share/nginx/www  \n#COPY archive /usr/share/nginx/archive\n```\n\nAnd the content of *ngnix.conf*.\n\n```conf\nworker_processes 1;\n\nevents { worker_connections 1024; }\n\nhttp {\n    sendfile on;\n\n\tserver {\n\t\tlisten 80;\n\t\tserver_name localhost;\n\n\t\tproxy_set_header Host $host;\n\t\tproxy_set_header X-Forwarded-For $remote_addr;\n\n\n\t\tlocation /users {\n\t\t\tproxy_pass http://user-service:8001;\n\t\t}\n\t\tlocation /posts {\n\t\t\tproxy_pass http://post-service:8002;\n\t\t}\n\t\tlocation / {\n\t\t\tproxy_pass http://auth-service:8000;\n\t\t}\n\t}\n}\n```\n\nCreate a standalone docker-compose.local.yml file to run all services.\n\n```yml\nversion: '3.1' # specify docker-compose version\n\nservices:\n\n  nginx-proxy:\n    image: hantsy/nginx-proxy\n    container_name: nginx-proxy\n    build: \n      context: ./nginx\n      dockerfile: Dockerfile\n    depends_on:\n      - auth-service\n      - user-service\n      - post-service\n    ports:\n      - \"80:80\"\n      \n  auth-service:\n    image: hantsy/auth-service\n    container_name: auth-service\n    build: \n      context: ./auth-service # specify the directory of the Dockerfile\n      dockerfile: Dockerfile\n    environment:\n      USERDB_URL: jdbc:mysql://userdb:3306/userdb\n      REDIS_HOST: redis\n    ports:\n      - \"8000:8000\" #specify ports forewarding\n    depends_on:\n      - userdb\n      - redis\n      \n  user-service: \n    image: hantsy/user-service\n    container_name: user-service\n    build: \n      context: ./user-service\n      dockerfile: Dockerfile\n    environment:\n      USERDB_URL: jdbc:mysql://userdb:3306/userdb\n      REDIS_HOST: redis\n    ports:\n      - \"8001:8001\" #specify ports forewarding\n    depends_on:\n      - userdb\n      - redis\n  \n  post-service: \n    image: hantsy/post-service\n    container_name: post-service\n    build: \n      context: ./post-service\n      dockerfile: Dockerfile\n    environment:\n      POSTDB_URL: jdbc:mysql://postdb:3306/postdb\n      REDIS_HOST: redis\n    ports:\n      - \"8002:8002\" #specify ports forewarding\n    depends_on:\n      - postdb\n      - redis \n```\n\nRun all services in your local system or a staging server.\n\nBuild the project via `mvn` command.\n\n```\nmvn clean package -DskipTests\n```\n\nThen run the following command to run all services.\n\n```\ndocker-compose -f docker-compose.yml -f docker-compose.local.yml up --build\n```\n\nThe `--build` parameter tells Docker build Docker images for all services firstly, then create containers based on the built images.  \n\nWe have run a Nginx a reverse proxy, all APIs can be accessed through a single entry. \n\nThe following services will be provided.\n\n| Service      | Url                                      | Description                              |\n| ------------ | ---------------------------------------- | ---------------------------------------- |\n| auth-service | http://localhost/user,http://localhost/auth | Authentication APIs(signin, signup, signout), user info |\n| user-service | http://localhost/users                   | User management APIs                     |\n| post-service | http://localhost/posts                   | Post and comment APIs                    |\n\nNext, let's try the endpoints by `curl` command.\n\nGet authentication by sending user/password pair via  HTTP BASIC header.\n\n```\ncurl -v  http://localhost/user -u user:test123\n\n\u003e\n\u003c HTTP/1.1 200\n\u003c Server: nginx/1.13.0\n\u003c Date: Thu, 25 May 2017 06:49:52 GMT\n\u003c Content-Type: application/json;charset=UTF-8\n\u003c Transfer-Encoding: chunked\n\u003c Connection: keep-alive\n\u003c X-Content-Type-Options: nosniff\n\u003c X-XSS-Protection: 1; mode=block\n\u003c Cache-Control: no-cache, no-store, max-age=0, must-revalidate\n\u003c Pragma: no-cache\n\u003c Expires: 0\n\u003c X-Frame-Options: DENY\n\u003c X-Auth-Token: 8b185a90-37db-444a-832b-6cbcd6db6df8\n\u003c\n{\"name\":\"user\",\"roles\":[\"USER\"]}* Connection #0 to host localhost left intact\n```\n\nAs you see the response headers includes a **X-Auth-Token** item.\n\nThen add this header to the request headers when creating a new post, it return a successful *CREATED* status, and the new created post can be located via *Location* header in the response.\n\n```\ncurl -v  http://localhost/posts -X POST -H \"X-Auth-Token: 8b185a90-37db-444a-832b-6cbcd6db6df8\" -H \"Content-Type:application/json\" -d \"{\\\"title\\\": \\\"test post\\\", \\\"content\\\":\\\"test content of post\\\"}\"\nNote: Unnecessary use of -X or --request, POST is already inferred.\n* timeout on name lookup is not supported\n*   Trying ::1...\n* TCP_NODELAY set\n*   Trying 127.0.0.1...\n* TCP_NODELAY set\n* Connected to localhost (127.0.0.1) port 80 (#0)\n\u003e POST /posts HTTP/1.1\n\u003e Host: localhost\n\u003e User-Agent: curl/7.54.0\n\u003e Accept: */*\n\u003e X-Auth-Token: 8b185a90-37db-444a-832b-6cbcd6db6df8\n\u003e Content-Type:application/json\n\u003e Content-Length: 56\n\u003e\n* upload completely sent off: 56 out of 56 bytes\n\u003c HTTP/1.1 201\n\u003c Server: nginx/1.13.0\n\u003c Date: Thu, 25 May 2017 06:52:46 GMT\n\u003c Content-Length: 0\n\u003c Connection: keep-alive\n\u003c X-Content-Type-Options: nosniff\n\u003c X-XSS-Protection: 1; mode=block\n\u003c Cache-Control: no-cache, no-store, max-age=0, must-revalidate\n\u003c Pragma: no-cache\n\u003c Expires: 0\n\u003c X-Frame-Options: DENY\n\u003c Location: http://localhost/posts/1\n\u003c\n* Connection #0 to host localhost left intact\n```\n\nCreate another new post.\n\n```\ncurl -v  http://localhost/posts -X POST -H \"X-Auth-Token: 8b185a90-37db-444a-832b-6cbcd6db6df8\" -H \"Content-Type:application/json\" -d \"{\\\"title\\\": \\\"test post 2\\\", \\\"content\\\":\\\"test content of post 2\\\"}\"\nNote: Unnecessary use of -X or --request, POST is already inferred.\n* timeout on name lookup is not supported\n*   Trying ::1...\n* TCP_NODELAY set\n*   Trying 127.0.0.1...\n* TCP_NODELAY set\n* Connected to localhost (127.0.0.1) port 80 (#0)\n\u003e POST /posts HTTP/1.1\n\u003e Host: localhost\n\u003e User-Agent: curl/7.54.0\n\u003e Accept: */*\n\u003e X-Auth-Token: 8b185a90-37db-444a-832b-6cbcd6db6df8\n\u003e Content-Type:application/json\n\u003e Content-Length: 60\n\u003e\n* upload completely sent off: 60 out of 60 bytes\n\u003c HTTP/1.1 201\n\u003c Server: nginx/1.13.0\n\u003c Date: Thu, 25 May 2017 06:53:29 GMT\n\u003c Content-Length: 0\n\u003c Connection: keep-alive\n\u003c X-Content-Type-Options: nosniff\n\u003c X-XSS-Protection: 1; mode=block\n\u003c Cache-Control: no-cache, no-store, max-age=0, must-revalidate\n\u003c Pragma: no-cache\n\u003c Expires: 0\n\u003c X-Frame-Options: DENY\n\u003c Location: http://localhost/posts/test-post-2\n\u003c\n* Connection #0 to host localhost left intact\n```\n\nGet all post, and verify the created posts.\n\n```\ncurl -v  http://localhost/posts  -H \"Accpet:application/json\"\n* timeout on name lookup is not supported\n*   Trying ::1...\n* TCP_NODELAY set\n*   Trying 127.0.0.1...\n* TCP_NODELAY set\n* Connected to localhost (127.0.0.1) port 80 (#0)\n\u003e GET /posts HTTP/1.1\n\u003e Host: localhost\n\u003e User-Agent: curl/7.54.0\n\u003e Accept: */*\n\u003e Accpet:application/json\n\u003e\n\u003c HTTP/1.1 200\n\u003c Server: nginx/1.13.0\n\u003c Date: Thu, 25 May 2017 06:53:58 GMT\n\u003c Content-Type: application/json;charset=UTF-8\n\u003c Transfer-Encoding: chunked\n\u003c Connection: keep-alive\n\u003c X-Content-Type-Options: nosniff\n\u003c X-XSS-Protection: 1; mode=block\n\u003c Cache-Control: no-cache, no-store, max-age=0, must-revalidate\n\u003c Pragma: no-cache\n\u003c Expires: 0\n\u003c X-Frame-Options: DENY\n\u003c\n{\n  \"content\" : [ {\n    \"title\" : \"test post 2\",\n    \"postSlug\" : \"test-post-2\",\n    \"status\" : \"DRAFT\",\n    \"id\" : 2,\n    \"createdDate\" : \"2017-05-25T06:53:30\",\n    \"author\" : {\n      \"username\" : \"user\"\n    }\n  }, {\n    \"title\" : \"test post\",\n    \"postSlug\" : \"test-post\",\n    \"status\" : \"DRAFT\",\n    \"id\" : 1,\n    \"createdDate\" : \"2017-05-25T06:52:45\",\n    \"author\" : {\n      \"username\" : \"user\"\n    }\n  } ],\n  \"pageable\" : {\n    \"sort\" : {\n      \"sorted\" : true,\n      \"unsorted\" : false\n    },\n    \"pageSize\" : 10,\n    \"pageNumber\" : 0,\n    \"offset\" : 0,\n    \"paged\" : true,\n    \"unpaged\" : false\n  },\n  \"last\" : true,\n  \"totalElements\" : 2,\n  \"totalPages\" : 1,\n  \"sort\" : {\n    \"sorted\" : true,\n    \"unsorted\" : false\n  },\n  \"numberOfElements\" : 2,\n  \"first\" : true,\n  \"size\" : 10,\n  \"number\" : 0\n}* Connection #0 to host localhost left intact\n```\n\nCreate a comment for \"test post 2\". Do not forget to add the **X-Auth-Token** header to the request headers.\n\n```\ncurl -v  http://localhost/posts/test-post-2/comments -X POST -H \"X-Auth-Token: 8b185a90-37db-444a-832b-6cbcd6db6df8\" -H \"Content-Type:application/json\" -d \"{ \\\"content\\\":\\\"conmment content of post 2\\\"}\"\nNote: Unnecessary use of -X or --request, POST is already inferred.\n* timeout on name lookup is not supported\n*   Trying ::1...\n* TCP_NODELAY set\n*   Trying 127.0.0.1...\n* TCP_NODELAY set\n* Connected to localhost (127.0.0.1) port 80 (#0)\n\u003e POST /posts/test-post-2/comments HTTP/1.1\n\u003e Host: localhost\n\u003e User-Agent: curl/7.54.0\n\u003e Accept: */*\n\u003e X-Auth-Token: 8b185a90-37db-444a-832b-6cbcd6db6df8\n\u003e Content-Type:application/json\n\u003e Content-Length: 41\n\u003e\n* upload completely sent off: 41 out of 41 bytes\n\u003c HTTP/1.1 201\n\u003c Server: nginx/1.13.0\n\u003c Date: Thu, 25 May 2017 06:54:59 GMT\n\u003c Content-Length: 0\n\u003c Connection: keep-alive\n\u003c X-Content-Type-Options: nosniff\n\u003c X-XSS-Protection: 1; mode=block\n\u003c Cache-Control: no-cache, no-store, max-age=0, must-revalidate\n\u003c Pragma: no-cache\n\u003c Expires: 0\n\u003c X-Frame-Options: DENY\n\u003c Location: http://localhost/posts/test-post-2/comments/3\n\u003c\n* Connection #0 to host localhost left intact\n```\n\nCreate another comment.\n\n```\ncurl -v  http://localhost/posts/test-post-2/comments -X POST -H \"X-Auth-Token: 8b185a90-37db-444a-832b-6cbcd6db6df8\" -H \"Content-Type:application/json\" -d \"{ \\\"content\\\":\\\"conmment content of post, another comment\\\"}\"\nNote: Unnecessary use of -X or --request, POST is already inferred.\n* timeout on name lookup is not supported\n*   Trying ::1...\n* TCP_NODELAY set\n*   Trying 127.0.0.1...\n* TCP_NODELAY set\n* Connected to localhost (127.0.0.1) port 80 (#0)\n\u003e POST /posts/test-post-2/comments HTTP/1.1\n\u003e Host: localhost\n\u003e User-Agent: curl/7.54.0\n\u003e Accept: */*\n\u003e X-Auth-Token: 8b185a90-37db-444a-832b-6cbcd6db6df8\n\u003e Content-Type:application/json\n\u003e Content-Length: 56\n\u003e\n* upload completely sent off: 56 out of 56 bytes\n\u003c HTTP/1.1 201\n\u003c Server: nginx/1.13.0\n\u003c Date: Thu, 25 May 2017 06:55:21 GMT\n\u003c Content-Length: 0\n\u003c Connection: keep-alive\n\u003c X-Content-Type-Options: nosniff\n\u003c X-XSS-Protection: 1; mode=block\n\u003c Cache-Control: no-cache, no-store, max-age=0, must-revalidate\n\u003c Pragma: no-cache\n\u003c Expires: 0\n\u003c X-Frame-Options: DENY\n\u003c Location: http://localhost/posts/test-post-2/comments/4\n\u003c\n* Connection #0 to host localhost left intact\n```\n\nNow get all comments of the post *test-post-2* to verify the comments.\n\n```\ncurl -v  http://localhost/posts/test-post-2/comments  -H \"Accpet:application/json\"\n* timeout on name lookup is not supported\n*   Trying ::1...\n* TCP_NODELAY set\n* connect to ::1 port 80 failed: Connection refused\n*   Trying 127.0.0.1...\n* TCP_NODELAY set\n* Connected to localhost (127.0.0.1) port 80 (#0)\n\u003e GET /posts/test-post-2/comments HTTP/1.1\n\u003e Host: localhost\n\u003e User-Agent: curl/7.54.0\n\u003e Accept: */*\n\u003e Accpet:application/json\n\u003e\n\u003c HTTP/1.1 200\n\u003c Server: nginx/1.13.0\n\u003c Date: Thu, 25 May 2017 06:55:35 GMT\n\u003c Content-Type: application/json;charset=UTF-8\n\u003c Transfer-Encoding: chunked\n\u003c Connection: keep-alive\n\u003c X-Content-Type-Options: nosniff\n\u003c X-XSS-Protection: 1; mode=block\n\u003c Cache-Control: no-cache, no-store, max-age=0, must-revalidate\n\u003c Pragma: no-cache\n\u003c Expires: 0\n\u003c X-Frame-Options: DENY\n\u003c\n{\n  \"content\" : [ {\n    \"content\" : \"conmment content of post, another comment\",\n    \"id\" : 4,\n    \"createdDate\" : \"2017-05-25T06:55:22\",\n    \"author\" : {\n      \"username\" : \"user\"\n    }\n  }, {\n    \"content\" : \"conmment content of post 2\",\n    \"id\" : 3,\n    \"createdDate\" : \"2017-05-25T06:54:59\",\n    \"author\" : {\n      \"username\" : \"user\"\n    }\n  } ],\n  \"pageable\" : {\n    \"sort\" : {\n      \"sorted\" : true,\n      \"unsorted\" : false\n    },\n    \"pageSize\" : 10,\n    \"pageNumber\" : 0,\n    \"offset\" : 0,\n    \"paged\" : true,\n    \"unpaged\" : false\n  },\n  \"last\" : true,\n  \"totalElements\" : 2,\n  \"totalPages\" : 1,\n  \"sort\" : {\n    \"sorted\" : true,\n    \"unsorted\" : false\n  },\n  \"numberOfElements\" : 2,\n  \"first\" : true,\n  \"size\" : 10,\n  \"number\" : 0\n}* Connection #0 to host localhost left intact\n```\n\n## Testing Microservices \n\nAs stated in the previous sections, every single service is a small Spring Boot application. To test the whole Microservices application, firstly you should fully test the services/components themselves.\n\n### Testing Single Service\n\nTesting a single service is similar to testing  a general Spring Boot application, for example, in this application, to test post service, you should test very components in this service. \n\n*  Simple  POJOs, such as `@Entity` classes, DTOs. \n*  Database related facilities, such as JPA and `Repository` classes.\n*  Web layer, such as `Controller`  and exception handlers. \n*  Additionally,  integration tests is a must to ensure the application is working well close to a real world deployment environment.\n\n#### Testing POJOs\n\nIt is very simple, like testing a single POJO classes in a Java application, no dependent object in it. An example of testing the `Post` entity class.\n\n```java\npublic class PostTest {\n\n    @Test\n    public void testSlug() {\n        System.out.println(\"getSlug\");\n        Post instance = new Post();\n        instance.setTitle(\"test post 1\");\n        instance.slugify();\n        assertEquals(\"test-post-1\", instance.getSlug());\n    }\n}\n```\n\n#### Testing Repository\n\nThere are some utilities can be used to test a Spring Data  `Repository` bean. \n\nFor Spring Data JPA, there is a `@DataJpaTest` annotation which will autoconfigure the essential dependencies for testing a `Repository` bean, that means  it does not load all beans from the application context when running the tests. And when adding an embedded RDBMS, such as H2 in the test classpath, it will bypass the real database configuration in the application properties and use the embedded database instead when running the tests.\n\nAdditionally, Spring Boot provides a `TestEntityManager` bean which is similar to the standard `EntityManager`, but provides more methods for test purpose.\n\nAdd H2 to test scope in the *pom.xml* file.\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.h2database\u003c/groupId\u003e\n    \u003cartifactId\u003eh2\u003c/artifactId\u003e\n    \u003cscope\u003etest\u003c/scope\u003e\n\u003c/dependency\u003e\n```\n\nCreate a test calss to test `PostRepository`.\n\n```java\n @RunWith(SpringRunner.class)\n @DataJpaTest()\n @Slf4j\n public class PostRepositoryTest {\n \n     @Autowired\n     private TestEntityManager em;\n \n     @Autowired\n     PostRepository posts;\n \n     @Before\n     public void setup() {\n         assertNotNull(\"posts is not null\", posts);\n         posts.deleteAllInBatch();\n         em.persist(Post.builder().title(\"test post 1\").content(\"test content of test post 1\").build());\n     }\n \n     @Test\n     public void testGetAllPosts() {\n         assertTrue(1 == posts.findAll().size());\n         Post post = posts.findAll().get(0);\n         assertTrue(\"test-post-1\".equals(post.getSlug()));\n     }\n \n }\n```\n\n \n\n#### Testing PostService \n\n The `PostService` depends on `PostRepsoitory` bean.  To test the internal logic of `PostService`, we can mock the dependent beans(eg. `PostRepository` bean) and stub the behavior of `PostRepository` bean, and verify the logic in `PostService` works as expected.\n\n```java\n@RunWith(SpringRunner.class)\n@Slf4j\npublic class PostServiceTest {\n\n    @MockBean\n    private PostRepository posts;\n\n    @Autowired\n    private PostService postService;\n\n\n    @Test\n    public void createPost() {\n        final String TITLE = \"test post title\";\n        final String CONTENT = \"test post content\";\n\n        final PostForm input = PostForm.builder().title(TITLE).content(CONTENT).build();\n        Post expected = Post.builder().title(TITLE).content(CONTENT).build();\n        expected.setId(1L);\n\n        given(posts.save(Post.builder().title(input.getTitle()).content(input.getContent()).build()))\n                .willReturn(expected);\n\n        Post returned = postService.createPost(input);\n\n        assertTrue(returned == expected);\n\n        verify(posts, times(1)).save(any(Post.class));\n        verifyNoMoreInteractions(posts);\n    }\n\n    @TestConfiguration\n    @Import(PostService.class)\n    static class TestConfig {\n\n    }\n\n}\n```\n\n\n\n####  Testing web facilities\n\nFor Spring WebMVC applications,  Spring Boot includes a simple `@WebMvcTest` to prepare the test environment for testing controller classes.  When running a test annotated with `@WebMvcTest`, a  `MockMvc`  bean is available in the application context. In the `@WebMvcTest`, use the `controllers`  to specify the controllers you wan to tests, and there is a  `secure` attribute indicates if enabling Spring Security support in this test.\n\n```java\n@RunWith(SpringRunner.class)\n@Slf4j\n@WebMvcTest(controllers = PostController.class, secure = false)\npublic class PostControllerTest {\n\n    @MockBean\n    PostRepository posts;\n\n    @MockBean\n    CommentRepository comments;\n\n    @MockBean\n    PostService postService;\n\n    @Autowired\n    ObjectMapper objectMapper;\n\n    @Autowired\n    MockMvc mockMvc;\n\n    @Test\n    public void createPost() throws Exception {\n        Post _data = Post.builder().slug(\"test-my-first-post\").title(\"my first post\").content(\"my content of my post\").build();\n        given(this.postService.createPost(any(PostForm.class)))\n                .willReturn(_data);\n\n        this.mockMvc\n                .perform(\n                        post(\"/posts\")\n                                .content(objectMapper.writeValueAsString(PostForm.builder().title(\"my first post\").content(\"my content of my post\").build()))\n                                .contentType(MediaType.APPLICATION_JSON)\n                )\n                .andExpect(status().isCreated())\n                .andExpect(header().exists(\"Location\"));\n\n        verify(this.postService, times(1)).createPost(any(PostForm.class));\n        verifyNoMoreInteractions(this.postService);\n    }\n\n}\n```\n\n\u003e  Note: In the latest Spring Boot, the **secure** attribute of `@WebMvcTest` is deprecated. If you want to exclude Spring Security configuration, you have to exclude the Spring Security related Configurations, see [this example](https://github.com/hantsy/spring-webmvc-jwt-sample/blob/master/src/test/java/com/example/demo/VehicleControllerTest.java#L32).\n\nFor fine grained configure the `MockMvc`,  you can create it through `MockMvcBuilders.standaloneSetup` or `MockMvcBuilders.webAppContextSetup`, the former will choose the controllers to tests, and the later will load all controllers from the application context. \n\nThe following is an example test using `MockMvcBuilders.standaloneSetup` to configure a `MockMvc` object.\n\n```java\n@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)\n@RunWith(SpringRunner.class)\n@Slf4j\npublic class ApplicationControllerMockMvcTest {\n\n    @Autowired\n    WebApplicationContext wac;\n\n    @MockBean\n    PostRepository posts;\n\n    @MockBean\n    CommentRepository comments;\n\n    @MockBean\n    PostService postService;\n\n    @Autowired\n    ObjectMapper objectMapper;\n\n    @Autowired\n    FilterChainProxy springSecurityFilterChain;\n\n    MockMvc mockMvc;\n\n    @Before\n    public void setup() {\n        this.mockMvc = standaloneSetup(new PostController(postService, posts, comments))\n                .setCustomArgumentResolvers(\n                        new PageableHandlerMethodArgumentResolver()\n                )\n                .setMessageConverters(\n                        new MappingJackson2HttpMessageConverter(objectMapper)\n                )\n                .alwaysDo(print())\n                .apply(springSecurity(springSecurityFilterChain))\n                .build();\n    }\n\n    @Test\n    //@Ignore\n    public void testGetAllPosts() throws Exception {\n        given(this.posts\n                .findAll(any(Specification.class), any(Pageable.class)))\n                .willReturn(\n                        new PageImpl(\n                                Arrays.asList(\n                                        Post.builder().title(\"my first post1\").content(\"my content of my post1\").build(),\n                                        Post.builder().title(\"my first post2\").content(\"my content of my post2\").build(),\n                                        Post.builder().title(\"my first post3\").content(\"my content of my post3\").build()\n                                ),\n                                PageRequest.of(0, 10),\n                                3L\n                        )\n                );\n\n        MvcResult result = this.mockMvc\n                .perform(\n                        get(\"/posts?q=my\")\n                                .accept(MediaType.APPLICATION_JSON)\n                )\n                .andExpect(status().isOk())\n                .andExpect(jsonPath(\"$.content[*].title\", hasItem(\"my first post1\")))\n                .andReturn();\n\n        log.debug(\"mvc result:::\" + result.getResponse().getContentAsString());\n        verify(this.posts, times(1)).findAll(any(Specification.class), any(Pageable.class));\n        verifyNoMoreInteractions(this.posts);\n    }\n\n    @Test\n    public void createPostWithoutAuthentication() throws Exception {\n        Post _data = Post.builder().title(\"my first post\").content(\"my content of my post\").build();\n        given(this.postService.createPost(any(PostForm.class)))\n                .willReturn(_data);\n\n        MvcResult result = this.mockMvc\n                .perform(\n                        post(\"/posts\")\n                                .content(objectMapper.writeValueAsString(PostForm.builder().title(\"my first post\").content(\"my content of my post\").build()))\n                                .contentType(MediaType.APPLICATION_JSON)\n                )\n                .andExpect(status().isUnauthorized())\n                .andReturn();\n\n        log.debug(\"mvc result::\" + result.getResponse().getContentAsString());\n\n        verify(this.postService, times(0)).createPost(any(PostForm.class));\n        verifyNoMoreInteractions(this.postService);\n    }\n\n    @Test\n    @WithMockUser\n    public void createPostWithMockUser() throws Exception {\n        Post _data = Post.builder().title(\"my first post\").content(\"my content of my post\").build();\n        given(this.postService.createPost(any(PostForm.class)))\n                .willReturn(_data);\n\n        MvcResult result = this.mockMvc\n                .perform(\n                        post(\"/posts\")\n                                .content(objectMapper.writeValueAsString(PostForm.builder().title(\"my first post\").content(\"my content of my post\").build()))\n                                .contentType(MediaType.APPLICATION_JSON)\n                )\n                .andExpect(status().isCreated())\n                .andExpect(header().string(HttpHeaders.LOCATION, containsString(\"/posts\")))\n                .andReturn();\n\n        log.debug(\"mvc result::\" + result.getResponse().getContentAsString());\n\n        verify(this.postService, times(1)).createPost(any(PostForm.class));\n    }\n\n}\n```\n\nSimilarly you can build a `MockMvc` object using `MockMvcBuilders.webAppContextSetup`.\n\n```java\nthis.mockMvc = webAppContextSetup(this.wac)\n    .alwaysDo(print())\n    .apply(springSecurity(springSecurityFilterChain))\n    .build();\n```\n\nRestAssured also extends the MockMvc support through `io.rest-assured:spring-mock-mvc` module, explore the [RestAsssured MockMVC integration example](https://github.com/hantsy/spring-microservice-sample/blob/master/post-service/src/test/java/com/example/post/ApplicationRestAssuredMockMvcTest.java) yourself.\n\nNext let's move on  a small feature, I've created a `View` class to limit the result in the final JSON view.  Let's create a test for verify it.  Spring Boot provides a `@JsonTest` and allow you test the JSON serialization and deserialization. \n\n```java\n@RunWith(SpringRunner.class)\n@JsonTest\n@Slf4j\npublic class JsonViewTest {\n\n    @Autowired\n    private JacksonTester\u003cPost\u003e json;\n\n    @Test\n    public void serializeJson() throws IOException {\n        Post details = Post.builder().title(\"test title\").content(\"test content\").build();\n\n        assertThat(this.json.write(details)).extractingJsonPathStringValue(\"@.title\")\n                .isEqualTo(\"test title\");\n        assertThat(this.json.write(details)).extractingJsonPathStringValue(\"@.content\")\n                .isEqualTo(\"test content\");\n\n    }\n\n    @Test\n    public void serializeJsonWithView() throws IOException {\n        Post details = Post.builder().title(\"test title\").content(\"test content\").build();\n\n        ObjectMapper mapper = new ObjectMapper();\n        String result = mapper\n                .writerWithView(View.Summary.class)\n                .writeValueAsString(details);\n        log.debug(\"result:::\" + result);\n\n        assertTrue(result.contains(\"test title\"));\n        assertTrue(!result.contains(\"test content\"));\n\n    }\n}\n```\n\n\n\n#### Integration Tests\n\nNow web run the application with all dependent services, esp. with the real database.  \n\nThe following is a sample integration tests. \n\n```java\n@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)\n@RunWith(SpringRunner.class)\n@Slf4j\npublic class IntegrationTests {\n\n    @LocalServerPort\n    int port;\n\n    @Autowired\n    PostRepository posts;\n\n    @Autowired\n    CommentRepository comments;\n\n    String test_title = \"test title\";\n    String test_content = \"test content\";\n    String test_comment = \"test_comment\";\n    String slug = \"\";\n\n    @Before\n    public void setup() {\n        RestAssured.reset();\n        RestAssured.port = this.port;\n        this.comments.deleteAllInBatch();\n        this.posts.deleteAllInBatch();\n\n        Post post = posts.save(\n                Post.builder()\n                        .title(test_title)\n                        .content(test_content)\n                        .build()\n        );\n        log.debug(\"saved post:\" + post);\n        this.slug = post.getSlug();\n\n        log.debug(\"print all posts:\");\n        posts.findAll().forEach(System.out::println);\n\n        Comment comment = this.comments.save(\n                Comment.builder()\n                        .content(test_comment)\n                        .post(new PostSlug(this.slug))\n                        .build()\n        );\n        log.debug(\"saved comment:\" + comment);\n    }\n\n    @Test\n    public void testGetNoneExistingPost_shouldReturn404() throws Exception {\n        //@formatter:off\n        when()\n            .get(\"/posts/100000\")\n        .then()\n            .statusCode(HttpStatus.SC_NOT_FOUND);\n        //@formatter:on\n    }\n\n\n    @Test\n    public void testGetAllPosts_shouldBeOK() throws Exception {\n        //@formatter:off\n        when()\n            .get(\"/posts\")\n        .then()\n            .body(\"content[0].title\", is(test_title))\n            .statusCode(HttpStatus.SC_OK);\n        //@formatter:on\n    }\n\n    @Test\n    public void testGetPostBySlug_shouldBeOK() throws Exception {\n        //@formatter:off\n        when()\n            .get(\"/posts/\"+ this.slug)\n        .then()\n            .body(\"title\", is(test_title))\n            .body(\"content\", is(test_content))\n            .statusCode(HttpStatus.SC_OK);\n        //@formatter:on\n    }\n\n    @Test\n    public void testGetCommentsOfPostBySlug_shouldBeOK() throws Exception {\n        //@formatter:off\n        when()\n            .get(\"/posts/\"+ this.slug+\"/comments\")\n        .then()\n            .body(\"content[0].content\", is(test_comment))\n            .statusCode(HttpStatus.SC_OK);\n        //@formatter:on\n    }\n\n    //-------------- test with auth -----------------------\n    @Test\n    public void testCreateAPost_withoutUserAuth_shouldReturn401() throws Exception {\n        PostForm _data = PostForm.builder().title(test_title).content(test_content).build();\n\n        //@formatter:off\n        given()\n            //.auth().basic(\"user\", \"password\")\n            .body(_data)\n            .contentType(ContentType.JSON)\n        .when()\n            .post(\"/posts\")\n        .then()\n            .statusCode(HttpStatus.SC_UNAUTHORIZED);\n        //@formatter:on\n    }\n\n    @Test\n    public void testCreateAPost_withUserAuth_shouldBeOK() throws Exception {\n        PostForm _data = PostForm.builder().title(test_title).content(test_content).build();\n\n        //@formatter:off\n        given()\n            .auth().basic(\"user\", \"password\")\n            .body(_data)\n            .contentType(ContentType.JSON)\n        .when()\n            .post(\"/posts\")\n        .then()\n            .header(\"Location\", containsString(\"/posts\"))\n            .statusCode(HttpStatus.SC_CREATED);\n        //@formatter:on\n    }\n\n\n    @Test\n    public void testUpdateAPost_withoutUserAuth_shouldReturn401() throws Exception {\n        PostForm _data = PostForm.builder().title(test_title).content(test_content).build();\n\n        //@formatter:off\n        given()\n            //.auth().basic(\"user\", \"password\")\n            .body(_data)\n            .contentType(ContentType.JSON)\n        .when()\n            .put(\"/posts/\"+ this.slug)\n        .then()\n            .statusCode(HttpStatus.SC_UNAUTHORIZED);\n        //@formatter:on\n    }\n\n    @Test\n    public void testUpdateAPost_withUserAuth_shouldBeOK() throws Exception {\n        PostForm _data = PostForm.builder().title(test_title).content(test_content).build();\n\n        //@formatter:off\n        given()\n            .auth().basic(\"user\", \"password\")\n            .body(_data)\n            .contentType(ContentType.JSON)\n        .when()\n            .put(\"/posts/\"+ this.slug)\n        .then()\n            .statusCode(HttpStatus.SC_NO_CONTENT);\n        //@formatter:on\n    }\n\n\n    @Test\n    public void testDeleteAPost_withoutAuth_shouldReturn401() throws Exception {\n\n        //@formatter:off\n        when()\n            .delete(\"/posts/\"+ this.slug)\n        .then()\n            .statusCode(HttpStatus.SC_UNAUTHORIZED);\n        //@formatter:on\n    }\n\n    @Test\n    public void testDeleteAPost_withUserAuth_shouldReturn403() throws Exception {\n\n        //@formatter:off\n        given()\n            .auth().basic(\"user\", \"password\")\n        .when()\n            .delete(\"/posts/\"+ this.slug)\n        .then()\n            .statusCode(HttpStatus.SC_FORBIDDEN);\n        //@formatter:on\n    }\n\n\n    @Test\n    public void testDeleteAPost_withAdminAuth_shouldOK() throws Exception {\n\n        //@formatter:off\n        given()\n            .auth().basic(\"admin\", \"password\")\n        .when()\n            .delete(\"/posts/\"+ this.slug)\n        .then()\n            .statusCode(HttpStatus.SC_NO_CONTENT);\n        //@formatter:on\n    }\n\n\n    @Test\n    public void testCreateACommentsOfPostBySlug_withoutAuth_shouldReturn401() throws Exception {\n        CommentForm _data = CommentForm.builder().content(test_comment).build();\n\n        //@formatter:off\n        given()\n            .body(_data)\n            .contentType(ContentType.JSON)\n        .when()\n            .post(\"/posts/\"+ this.slug+\"/comments\")\n        .then()\n            .statusCode(HttpStatus.SC_UNAUTHORIZED);\n        //@formatter:on\n    }\n\n    @Test\n    public void testCreateACommentsOfPostBySlug_withUserAuth_shouldBeOk() throws Exception {\n        CommentForm _data = CommentForm.builder().content(test_comment).build();\n\n        //@formatter:off\n        given()\n            .auth().basic(\"user\", \"password\")\n            .body(_data)\n            .contentType(ContentType.JSON)\n        .when()\n            .post(\"/posts/\"+ this.slug+\"/comments\")\n        .then()\n            .header(\"Location\", containsString(\"/posts/\"+ this.slug+\"/comments\"))\n            .statusCode(HttpStatus.SC_CREATED);\n        //@formatter:on\n    }\n\n    \n    @TestComponent\n    @Slf4j\n    static class TestUserDetailsService implements UserDetailsService {\n\n        private final PasswordEncoder passwordEncoder;\n\n        TestUserDetailsService(PasswordEncoder passwordEncoder) {\n            this.passwordEncoder = passwordEncoder;\n        }\n\n        @Override\n        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {\n            UserDetails user = User.withUsername(\"user\")\n                    .password(passwordEncoder.encode(\"password\"))\n                    .roles(\"USER\")\n                    .accountExpired(false)\n                    .accountLocked(false)\n                    .credentialsExpired(false)\n                    .disabled(false)\n                    .build();\n\n            UserDetails admin = User.withUsername(\"admin\")\n                    .password(passwordEncoder.encode(\"password\"))\n                    .roles(\"ADMIN\")\n                    .accountExpired(false)\n                    .accountLocked(false)\n                    .credentialsExpired(false)\n                    .disabled(false)\n                    .build();\n\n            log.debug(\"dummy user:\" + user);\n            log.debug(\"dummy admin:\" + admin);\n\n\n            if (\"user\".equals(username)) {\n                return user;\n            } else {\n                return admin;\n            }\n        }\n    }\n\n    @TestConfiguration\n    @Slf4j\n    @Import(TestUserDetailsService.class)\n    @Order(-1)\n    static class TestSecurityConfig extends WebSecurityConfigurerAdapter {\n\n        @Autowired\n        PasswordEncoder passwordEncoder;\n\n        @Autowired\n        UserDetailsService userDetailsService;\n\n        @Override\n        protected void configure(HttpSecurity http) throws Exception {\n            http\n                    .httpBasic()\n                    .and()\n                    .authorizeRequests()\n                    .antMatchers(HttpMethod.GET, \"/posts/**\").permitAll()\n                    .antMatchers(HttpMethod.DELETE, \"/posts/**\").hasRole(\"ADMIN\")\n                    .anyRequest().authenticated()\n                    .and()\n                    .csrf().disable();\n        }\n\n        @Override\n        protected void configure(AuthenticationManagerBuilder auth) throws Exception {\n            auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);\n        }\n\n        @Override\n        @Bean\n        public AuthenticationManager authenticationManagerBean() throws Exception {\n            return super.authenticationManagerBean();\n        }\n\n    }\n}\n```\n\nFrom the former sections, we have introduced how to get authentication from the **auth-service**. In the above codes, we add some custom Security configuration to override Spring [Security Config](https://github.com/hantsy/spring-microservice-sample/blob/master/post-service/src/main/java/com/example/post/PostServiceApplication.java#L51)  to isolate the authentication from **auth-service**. Here we use a simple HTTP Basic authentication instead.\n\n### Testing against External Service\n\nIn our application, the auth service depends on user service to complete the authentication process.  There is a `UserServiceClient` in the auth service used for signup and authentication.\n\n```java\n@Component\npublic class UserServiceClient {\n\n    private RestTemplate restTemplate;\n\n    private ObjectMapper objectMapper;\n\n    @Value(\"${services.user-service-url}\")\n    private String userServiceUrl;\n\n    public UserServiceClient(RestTemplateBuilder builder, ObjectMapper objectMapper) {\n        this.restTemplate = builder.build();\n        this.objectMapper = objectMapper;\n    }\n\n    public void handleSignup(SignupForm form) {\n        try {\n            ResponseEntity\u003cVoid\u003e response = this.restTemplate.postForEntity(userServiceUrl + \"/users\", form, Void.class);\n        } catch (HttpClientErrorException e) {\n            if (e.getStatusCode() == CONFLICT) {\n                Map map = null;\n                try {\n                    map = objectMapper.readValue(e.getResponseBodyAsByteArray(), Map.class);\n                } catch (IOException e1) {\n                    e1.printStackTrace();\n                }\n                throw new SignupConflictException((String) map.get(\"message\"));\n            }\n        }\n    }\n\n    public User findByUsername(String username) {\n        try {\n            ResponseEntity\u003cUser\u003e response = this.restTemplate.getForEntity(userServiceUrl + \"/users/{username}\", User.class, username);\n            return response.getBody();\n        } catch (HttpClientErrorException e) {\n            if (e.getStatusCode() == NOT_FOUND) {\n                return null;\n            }\n        }\n        return null;\n    }\n}\n```\n\nIn the above codes, we use `RestTemplate` to access the remote *user service*.  To test `UserServiceClient`, we have to mock a remote rest server  to ensure the endpoints is available when running the tests.  There are some several approaches to archive this.\n\n* Spring provides a `MockRestServiceServer` to setup a mock rest service easily.\n* [WireMock](http://wiremock.org/) is a more common solution for mocking HTTP endpoints.\n\nNext, let's explore them one by one.\n\n#### MockRestServiceServer\n\nThe following is an example using `MockRestServiceServer`.  The stubbing step is similar to the Mockito `when`/`given`, set the mocked data when submitting a specific request.\n\n```java\n@RunWith(SpringRunner.class)\n@RestClientTest(UserServiceClient.class)\n@Slf4j\npublic class UserServiceClientTest {\n\n    @Value(\"${services.user-service-url:http://localhost:8001}\")\n    private String userServiceUrl;\n\n    @Autowired\n    private UserServiceClient client;\n\n    @Autowired\n    private MockRestServiceServer server;\n\n    @Test\n    public void testFindbyUsername() {\n        this.server.expect(requestTo(userServiceUrl + \"/users/user\"))\n            .andRespond(withSuccess(new ClassPathResource(\"/find-user-by-username.json\"), MediaType.APPLICATION_JSON_UTF8));\n            //.andRespond(withSuccess(\"{\\\"username\\\":\\\"user\\\",\\\"password\\\":\\\"password\\\",\\\"email\\\":\\\"user@example.com\\\"}\", MediaType.APPLICATION_JSON_UTF8));\n\n        User user = this.client.findByUsername(\"user\");\n        assertNotNull(user);\n        assertEquals(\"user\", user.getUsername());\n\n        this.server.verify();\n    }\n\n    @Test\n    public void testFindbyUsername_notFound() {\n        this.server.expect(requestTo(userServiceUrl + \"/users/user1\"))\n            .andRespond(withStatus(NOT_FOUND));\n\n        User user = this.client.findByUsername(\"user1\");\n        assertNull(user);\n\n        this.server.verify();\n    }\n}\n```\n\nSimilar to Spring Boot `@WebMvcTest`, `@DataJpaTest`, etc.  The `@RestClientTest` is also a *slice test* utility which only provides a `RestTemplateBuilder` in the test context.\n\n#### WireMock\n\nWireMock is a general purpose solution for mocking HTTP endpoints.  \n\nThere is an example of using WireMock to test `UserServiceClient` .\n\n```java\n@RunWith(SpringRunner.class)\n@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)\n@Slf4j\npublic class UserServiceClientWireMockTest {\n\n    @Value(\"${services.user-service-url:http://localhost:8001}\")\n    private String userServiceUrl;\n\n    @Autowired\n    private UserServiceClient client;\n\n    @Rule\n    public WireMockRule wireMockRule = new WireMockRule(options().port(8001));\n\n    @Before\n    public void setup() {\n        WireMock.reset();\n    }\n\n    @Test\n    public void testFindbyUsername() {\n\n        stubFor(\n            get(\"/users/user\")\n                //.withHeader(\"Accept\", equalTo(MediaType.APPLICATION_JSON_VALUE))\n                .willReturn(\n                    okJson(\"{\\\"username\\\":\\\"user\\\",\\\"password\\\":\\\"password\\\",\\\"email\\\":\\\"user@example.com\\\"}\")\n                )\n        );\n\n        User user = this.client.findByUsername(\"user\");\n        assertNotNull(user);\n        assertEquals(\"user\", user.getUsername());\n\n        verify(1, getRequestedFor(urlMatching( \"/users/user\")));\n    }\n\n    @Test\n    public void testFindbyUsername_notFound() {\n        stubFor(\n            get( \"/users/user1\")\n                //.withHeader(\"Accept\", equalTo(MediaType.APPLICATION_JSON_VALUE))\n                .willReturn(\n                    aResponse()\n                        .withStatus(HttpStatus.SC_NOT_FOUND)\n                )\n        );\n\n        User user = this.client.findByUsername(\"user1\");\n        assertNull(user);\n\n        verify(1, getRequestedFor(urlMatching( \"/users/user1\")));\n    }\n}\n```\n\nIn the above codes, WireMock provides a  `@Rule` to wire the stubbing into the lifecycle of the JUnit runner. \n\n\n\n### Testing Service-to-Service Communication\n\nThe above `MockRestServiceServer` or `WireMock` is widely used when the existing external service is out of control, eg. it is from the 3rd party  company or organization.\n\nIn our application, the auth service and user service are developed by ourselves, but may be produced by two different teams. \n\nAssume when the auth service requires to embed  the result of  a `/users` endpoints that should be provided by user service, but at that moment such an AP does not exist in the user service at all.  To resolve the problem, the best way is the developers from two sides sit down at a table and *sign a contract* about the communication details between these two services.  Firstly the auth service developer lists all required HTTP endpoints that should be provided by the user service. For example.\n\n* When sending a  `GET`  request on the endpoints  `/users/user1`, then return a  response  with the json content like this: `{'username':'uesr', roles:'USER'}`, etc.\n* When  the requesting endpoint is `/users/noneexisting`, then return a 404 error. \n* ...\n\nThe user service developer reviews the requirements, and confirm the items one by one, and make sure they are on the same page. \n\nThen they are back to work and focus on their own development. When the development(both side) is done, they can use a real world environment to verify if they have complied with the rules defined in the contracts they have signed. \n\nIn the software development world, this kind of scene is called *Consumer Driven Contracts*. In the CDC world,  the auth service is called the API consumer, and the user service is the API producer/provider. In these years, CDC/Contracts testing becomes more and more popular.\n\nObviously, an advantage of applying this pattern is the consumer side can start work immediately when the contract is *signed* and do not need to wait for the complete work from the producer side.\n\nThere are a few projects available to improve the CDC development process.\n\n*  Spring Cloud includes a [Spring Cloud Contracts](https://spring.io/projects/spring-cloud-contract) subproject, which is heavily dependent on the Spring ecosystem.\n*  [Pact](https://pact.io/) is a general HTTP endpoints contracts verification solution, not limited to Spring ecosystem.\n\n#### Spring Cloud Contracts\n\n[Spring Cloud Contracts Workshop](https://spring-cloud-samples.github.io/spring-cloud-contract-samples/workshops.html) provides the best practice when introducing Spring Cloud Contracts into your Microservices project.\n\n\n\n#### Pact\n\n\n\n\n\n## Deploying Microservices application\n\nIn this section, we will explore how to deploy the services to the popular container platform, including  Docker Swarm and Kubernetes.\n\n### Publishing Docker Images to Docker Hub\n\nCreate an account on the official [Docker Hub](https://hub.docker.com/), after the account is created, you will get a special namespace for yourself. \n\nIn former steps, we have set the image name with a *hantsy/* prefix where the hantsy is the account name in the DockerHub.\n\nAfter the Docker is installed, you can login to docker hub in the terminal.\n\n```bash\ndocker login\n\n// follow the guide to input user name and password to log in.\n```\n\n Then run the following command to publish your Docker images to the public Docker Hub.\n\n```bash\ndocker push hantsy/post-service\ndocker push hantsy/user-service\ndocker push hantsy/auth-service\ndocker push hantsy/ngnix-proxy\n```\n\nWhen all are finished, go to  [Docker Hub](https://hub.docker.com/), login and you will see the uploaded Docker images. \n\nTo verify the Docker Images is available via DockerHub, run the following commands to pull them from Docker Hub.  \n\n```bash\n//remove the existing images.\ndocker rmi post-service\n\n//pull the docker images\ndocker pull hantsy/post-service\n```\n\nIf you do not want to expose your docker images to the public, choose a paid service or setup a private Docker registry server.\n\nNo panic the official docker registry is available as a Docker image, follow the official guide to [deploy a docker registry server](https://docs.docker.com/registry/deploying/).\n\nBesides the official Docker Hub and private Docker registry, almost all cloud platforms provide private Docker registry service for the customers. \n\nAnd Github and GitLab provides a Packages feature which includes hosting Docker images services, check the following docs:\n\n* [Working with Github Packages Registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry)\n* [GitLab Container Registry](https://docs.gitlab.com/ee/user/packages/container_registry/)\n\n### Deploying to Docker Swarm\n\nUse Docker Machine to create multiple nodes. \n\n\u003e These steps are tested on the legacy Docker Toolbox and use VritualBox as virtual machines. If you are using Docker Desktop for Windows,  use Hyper-V instead.\n\nIn order to demonstrate running this project in Swarm mode, we will create two managers and three workers.\n\n```\n$ docker-machine create -d virtualbox --engine-registry-mirror https://docker.mirrors.ustc.edu.cn manager1\n$ docker-machine create -d virtualbox --engine-registry-mirror https://docker.mirrors.ustc.edu.cn manager2\n$ docker-machine create -d virtualbox --engine-registry-mirror https://docker.mirrors.ustc.edu.cn worker1\n$ docker-machine create -d virtualbox --engine-registry-mirror https://docker.mirrors.ustc.edu.cn worker2\n$ docker-machine create -d virtualbox --engine-registry-mirror https://docker.mirrors.ustc.edu.cn worker3\n```\n\nList all docker machines you just created.\n\n```\n$ docker-machine ls\nNAME       ACTIVE   DRIVER       STATE     URL                         SWARM   DOCKER        ERRORS\nmanager1   -        virtualbox   Running   tcp://192.168.99.101:2376           v17.05.0-ce\nmanager2   -        virtualbox   Running   tcp://192.168.99.102:2376           v17.05.0-ce\nworker1    -        virtualbox   Running   tcp://192.168.99.103:2376           v17.05.0-ce\nworker2    -        virtualbox   Running   tcp://192.168.99.104:2376           v17.05.0-ce\nworker3    -        virtualbox   Running   tcp://192.168.99.105:2376           v17.05.0-ce\n```\n\nSwitch to machine `manager1`.\n\n```\neval \"$(docker-manager env manager1)\"\n```\n\nTry to initialize a Docker Swarm host.\n\n```\n$ docker swarm init --listen-addr 192.168.99.101 --advertise-addr 192.168.99.101\nSwarm initialized: current node (t36lxk020fasw5tdes4gm9ucf) is now a manager.\n\nTo add a worker to this swarm, run the following command:\n\n    docker swarm join \\\n    --token SWMTKN-1-10bwwj2u6erepp9oc0qlkwao4o79vogifon51qkhdqfsl7zkkd-810eddvkzt2g8vvxb4gul4pnb \\\n    192.168.99.101:2377\n\nTo add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.\n```\n\nWe want to add *manager2* as manager in this swarm. Follow the above info. Execute `docker swarm join-token manager`, it will show the guide to add more managers.\n\n```\n$ docker swarm join-token manager\nTo add a manager to this swarm, run the following command:\n\n    docker swarm join \\\n    --token SWMTKN-1-10bwwj2u6erepp9oc0qlkwao4o79vogifon51qkhdqfsl7zkkd-4xus5y6wa7a4ass0f5bt20pym \\\n    192.168.99.101:2377\n```\n\nLet us switch to machine *manager2*.\n\n```\neval \"$(docker-machine env manager2)\"\n```\n\nCopy and paste the `docker swarm join` command lines and execute it.\n\n```\n$ docker swarm join \\\n     --token SWMTKN-1-10bwwj2u6erepp9oc0qlkwao4o79vogifon51qkhdqfsl7zkkd-4xus5y6wa7a4ass0f5bt20pym \\\n     192.168.99.101:2377\nThis node joined a swarm as a manager.\n```\n\nSwitch to worker1, worker2, and worker3, join this swarm as a worker.\n\n```\n    docker swarm join \\\n    --token SWMTKN-1-10bwwj2u6erepp9oc0qlkwao4o79vogifon51qkhdqfsl7zkkd-810eddvkzt2g8vvxb4gul4pnb \\\n    192.168.99.101:2377\n```\n\nSwitch to any **manager** machine, and you can show all running nodes.\n\n```\n$ docker node ls\nID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS\n9d07by6czpem6hx55ke3ks1v1     manager2            Ready               Active              Reachable\ner9klqvww0kdwyfaxr5f7n15l     worker1             Ready               Active\nhsmaugexj4l7p5ighl9nega8q     worker2             Ready               Active\nlknqw5dg5jyxw3j2camcpnb0v *   manager1            Ready               Active              Leader\novqfs7ymrgbeyfqu8db8n6apc     worker3             Ready               Active\n```\n\nSwitch to any **manager** machine, deploy all service via `docker stack` command.\n\n```\ndocker stack deploy -c docker-stack.yml blogapp\n```\n\nThe services will be scheduled to deploy in this swarm.\n\n\nThe *docker-stack.yml* file includes a `visualizer` service to visualize all services. It can be accessed via *http://\u0026lt;any manager ip\u0026gt;:8080*, you will see the deployment progress.\n\n![visualizer](./docker-viz.png)\n\n\n\n```\n#curl http://192.168.99.102/user -u user:test123\n{\"roles\":[\"ROLE_USER\"],\"name\":\"user\"}\n```\n\nRemove this stack by the following command.\n\n```\ndocker stack rm blogapp\n```\n\n### Deploying to Kubernetes\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhantsy%2Fspring-microservice-sample","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhantsy%2Fspring-microservice-sample","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhantsy%2Fspring-microservice-sample/lists"}