├── README.md
├── backend
├── api-gateway-service
│ ├── pom.xml
│ └── src
│ │ └── main
│ │ ├── java
│ │ └── com
│ │ │ └── tushar
│ │ │ └── crypto
│ │ │ └── apigateway
│ │ │ └── APIGatewayServer.java
│ │ └── resources
│ │ └── application.properties
├── service-discovery
│ ├── pom.xml
│ └── src
│ │ └── main
│ │ ├── java
│ │ └── org
│ │ │ └── tushar
│ │ │ └── user
│ │ │ └── ServiceDiscoveryApplication.java
│ │ └── resources
│ │ └── application.properties
└── user-registration
│ ├── pom.xml
│ └── src
│ ├── main
│ ├── java
│ │ └── com
│ │ │ └── tushar
│ │ │ └── usermanagement
│ │ │ ├── RabbitMQReceiver.java
│ │ │ ├── UserManagementApplication.java
│ │ │ ├── config
│ │ │ └── UserRegistrationConfig.java
│ │ │ ├── exception
│ │ │ └── CustomResponse.java
│ │ │ ├── message
│ │ │ └── UserRegistrationNotification.java
│ │ │ ├── model
│ │ │ └── User.java
│ │ │ ├── repository
│ │ │ └── UserManagementRepository.java
│ │ │ ├── request
│ │ │ └── RegistrationRequest.java
│ │ │ ├── resources
│ │ │ └── UserManagementController.java
│ │ │ └── service
│ │ │ └── UserManagementService.java
│ └── resources
│ │ └── application.properties
│ └── module-info.java
├── images
├── Error.PNG
├── Success.PNG
├── UserRegistrationForm.PNG
└── diagram.png
└── ui
├── package.json
├── public
└── index.html
└── src
├── App.js
├── components
├── Error.js
├── Success.js
└── UserRegistration.js
├── constants
└── Constants.js
├── index.js
└── serviceWorker.js
/README.md:
--------------------------------------------------------------------------------
1 | # user-registration using microservices
2 | implementing user registration as part of an application that has a micro-service architecture
3 |
4 |
5 | Here we are implementing user registration as part of an application that has a micro-service architecture. Users register by
6 | entering their email address and a password. The system then initiates an account creation workflow that includes creating the account in the database and sending an email to confirm their address.
7 |
8 | The following diagram shows the user registration service and how it fits into the overall system architecture.
9 |
10 | 
11 |
12 |
13 | The backend user registration service exposes a single RESTful endpoint for registering users. A registration request contains the user’s email address and password. The service verifies that a user with that email address has not previously registered and publishes a message notifying the rest of the system that a new user has registered. The notification is consumed by various other services including the user management service, which maintains user accounts, and the email service, which sends a confirmation email to the user.
14 |
15 |
16 | ```
17 | File Structure
18 |
19 | ├───backend
20 | │ ├───api-gateway-service
21 | │ │ └───src
22 | │ │ └───main
23 | │ │ ├───java
24 | │ │ │ └───com
25 | │ │ │ └───tushar
26 | │ │ │ └───crypto
27 | │ │ │ └───apigateway
28 | │ │ └───resources
29 | │ ├───service-discovery
30 | │ │ └───src
31 | │ │ └───main
32 | │ │ ├───java
33 | │ │ │ └───org
34 | │ │ │ └───tushar
35 | │ │ │ └───user
36 | │ │ └───resources
37 | │ └───user-registration
38 | │ ├───java
39 | │ │ └───com
40 | │ │ └───tushar
41 | │ │ └───usermanagement
42 | │ │ ├───config
43 | │ │ ├───exception
44 | │ │ ├───message
45 | │ │ ├───model
46 | │ │ ├───repository
47 | │ │ ├───request
48 | │ │ ├───resources
49 | │ │ └───service
50 | │ └───resources
51 | ├───images
52 | └───ui
53 | └───src
54 | ├───components
55 | └───constants
56 | ```
57 |
58 | Components Used:
59 |
60 | # UI
61 | * react
62 | * semantic-ui-react
63 | * hooks
64 |
65 | # UI Install:
66 | * create-react-app ui
67 | * yarn i semantic-ui-css
68 | * yarn i semantic-ui-react
69 | * yarn i eslint-plugin-react-hooks
70 |
71 | # User Form
72 | 
73 |
74 | # Success Message
75 | 
76 |
77 | # Error Message
78 | 
79 |
80 |
81 |
82 | # Backend
83 | * Spring Boot
84 | * API Gateway server (Zuul)
85 | * Service Discovery (Eureka)
86 | * MongoDB
87 | * AMQP (RabbitMQ)
88 |
89 |
90 |
--------------------------------------------------------------------------------
/backend/api-gateway-service/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | org.tushar.user
7 | apigateway-service
8 | 0.0.1-SNAPSHOT
9 | jar
10 |
11 | apigateway-service
12 | Demo project for Spring Boot
13 |
14 |
15 | org.springframework.boot
16 | spring-boot-starter-parent
17 | 2.0.5.RELEASE
18 |
19 |
20 |
21 |
22 | UTF-8
23 | UTF-8
24 | 1.8
25 | Finchley.SR1
26 |
27 |
28 |
29 |
30 | org.springframework.boot
31 | spring-boot-starter-actuator
32 |
33 |
34 | org.springframework.boot
35 | spring-boot-starter-web
36 |
37 |
38 | org.springframework.cloud
39 | spring-cloud-starter-netflix-eureka-client
40 |
41 |
42 | org.springframework.cloud
43 | spring-cloud-starter-netflix-zuul
44 |
45 |
46 |
47 | org.springframework.boot
48 | spring-boot-devtools
49 | runtime
50 |
51 |
52 |
53 |
54 |
55 |
56 | org.springframework.boot
57 | spring-boot-starter-test
58 | test
59 |
60 |
61 |
62 |
63 |
64 |
65 | org.springframework.cloud
66 | spring-cloud-dependencies
67 | ${spring-cloud.version}
68 | pom
69 | import
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | org.springframework.boot
78 | spring-boot-maven-plugin
79 |
80 |
81 |
82 |
83 |
84 |
85 |
--------------------------------------------------------------------------------
/backend/api-gateway-service/src/main/java/com/tushar/crypto/apigateway/APIGatewayServer.java:
--------------------------------------------------------------------------------
1 | package com.tushar.crypto.apigateway;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
6 | import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
7 |
8 | @EnableZuulProxy
9 | @SpringBootApplication
10 | @EnableDiscoveryClient
11 | public class APIGatewayServer {
12 |
13 | public static void main(String[] args) {
14 | SpringApplication.run(APIGatewayServer.class, args);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/backend/api-gateway-service/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | server.port=8765
2 | spring.application.name=netflix-zul-service
3 | eureka.client.service-url.default-zone=http://localhost:8761/eureka
4 |
5 | zuul.host.socket-timeout-millis=60000
6 | zuul.host.connect-timeout-millis=10000
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/backend/service-discovery/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | org.tushar.user
7 | service.discovery
8 | 0.0.1-SNAPSHOT
9 | jar
10 |
11 | service-discovery
12 | Demo project for Spring Boot
13 |
14 |
15 | org.springframework.boot
16 | spring-boot-starter-parent
17 | 2.0.5.RELEASE
18 |
19 |
20 |
21 |
22 | UTF-8
23 | UTF-8
24 | 11
25 | Finchley.RELEASE
26 |
27 |
28 |
29 |
30 | org.springframework.boot
31 | spring-boot-starter-actuator
32 |
33 |
34 | org.springframework.cloud
35 | spring-cloud-starter-netflix-eureka-server
36 |
37 |
38 |
39 | org.springframework.boot
40 | spring-boot-devtools
41 | runtime
42 |
43 |
44 | org.springframework.boot
45 | spring-boot-starter-test
46 | test
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | org.springframework.cloud
57 | spring-cloud-dependencies
58 | ${spring-cloud.version}
59 | pom
60 | import
61 |
62 |
63 |
64 | jakarta.xml.bind
65 | jakarta.xml.bind-api
66 | 3.0.0
67 |
68 |
69 |
70 |
71 | com.sun.xml.bind
72 | jaxb-impl
73 | 3.0.0
74 | runtime
75 |
76 |
77 |
78 |
79 | jakarta.xml.bind
80 | jakarta.xml.bind-api
81 | 2.3.3
82 |
83 |
84 |
85 | com.sun.xml.bind
86 | jaxb-ri
87 | 2.3.3
88 |
89 |
90 |
91 | com.sun.xml.bind
92 | jaxb-impl
93 | 2.3.3
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 | org.springframework.boot
102 | spring-boot-maven-plugin
103 |
104 |
105 |
106 |
107 |
108 |
109 |
--------------------------------------------------------------------------------
/backend/service-discovery/src/main/java/org/tushar/user/ServiceDiscoveryApplication.java:
--------------------------------------------------------------------------------
1 | package org.tushar.user;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 | import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
6 |
7 | @SpringBootApplication
8 | @EnableEurekaServer
9 | public class ServiceDiscoveryApplication {
10 |
11 | public static void main(String[] args) {
12 | SpringApplication.run(ServiceDiscoveryApplication.class, args);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/backend/service-discovery/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | server.port=8761
2 | spring.application.name=service-discovery
3 | eureka.client.register-with-eureka=false
4 | eureka.client.fetch-registery=false
--------------------------------------------------------------------------------
/backend/user-registration/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | org.tushar.management
8 | user.registration
9 | 0.0.1-SNAPSHOT
10 | jar
11 |
12 | user-management
13 | Demo project for Spring Boot
14 |
15 |
16 | org.springframework.boot
17 | spring-boot-starter-parent
18 | 2.0.5.RELEASE
19 |
20 |
21 |
22 |
23 | UTF-8
24 | UTF-8
25 | 1.8
26 | Finchley.RELEASE
27 |
28 |
29 |
30 |
31 | org.springframework.boot
32 | spring-boot-starter-actuator
33 |
34 |
35 |
36 | com.fasterxml.jackson.core
37 | jackson-databind
38 | 2.9.2
39 |
40 |
41 |
42 |
43 | org.springframework.amqp
44 | spring-rabbit
45 | 2.0.8.RELEASE
46 |
47 |
48 | com.rabbitmq
49 | amqp-client
50 | 5.7.3
51 |
52 |
53 |
54 | org.springframework.boot
55 | spring-boot-starter-web
56 |
57 |
58 |
59 | javax.validation
60 | validation-api
61 | 2.0.1.Final
62 |
63 |
64 |
65 | org.mongodb
66 | mongo-java-driver
67 | 3.11.2
68 |
69 |
70 |
71 | org.springframework.data
72 | spring-data-mongodb
73 | 2.0.10.RELEASE
74 |
75 |
76 |
77 | org.springframework.cloud
78 | spring-cloud-starter-netflix-eureka-client
79 | 2.0.1.RELEASE
80 |
81 |
82 |
83 | org.springframework.boot
84 | spring-boot-starter-test
85 | test
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 | org.springframework.cloud
96 | spring-cloud-dependencies
97 | ${spring-cloud.version}
98 | pom
99 | import
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 | org.springframework.boot
108 | spring-boot-maven-plugin
109 |
110 |
111 |
112 |
113 |
114 |
115 |
--------------------------------------------------------------------------------
/backend/user-registration/src/main/java/com/tushar/usermanagement/RabbitMQReceiver.java:
--------------------------------------------------------------------------------
1 | package com.tushar.usermanagement;
2 |
3 | import java.util.concurrent.CountDownLatch;
4 |
5 | import org.springframework.amqp.core.Message;
6 | import org.springframework.amqp.rabbit.annotation.RabbitListener;
7 | import org.springframework.stereotype.Component;
8 |
9 | import com.tushar.usermanagement.message.UserRegistrationNotification;
10 |
11 |
12 | @Component
13 | public class RabbitMQReceiver {
14 |
15 | private CountDownLatch latch = new CountDownLatch(1);
16 |
17 | @RabbitListener(queues = "user-registration-queue")
18 | public void receiveMessage(Message message) {
19 | System.out.println("Received <" + message.toString() + ">");
20 | latch.countDown();
21 | }
22 |
23 | public CountDownLatch getLatch() {
24 | return latch;
25 | }
26 |
27 | }
--------------------------------------------------------------------------------
/backend/user-registration/src/main/java/com/tushar/usermanagement/UserManagementApplication.java:
--------------------------------------------------------------------------------
1 | package com.tushar.usermanagement;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 | import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
6 |
7 | @EnableMongoRepositories
8 | @SpringBootApplication
9 | public class UserManagementApplication {
10 |
11 | public static void main(String[] args) {
12 | SpringApplication.run(UserManagementApplication.class, args);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/backend/user-registration/src/main/java/com/tushar/usermanagement/config/UserRegistrationConfig.java:
--------------------------------------------------------------------------------
1 | package com.tushar.usermanagement.config;
2 |
3 | import org.springframework.amqp.core.Binding;
4 | import org.springframework.amqp.core.BindingBuilder;
5 | import org.springframework.amqp.core.Queue;
6 | import org.springframework.amqp.core.TopicExchange;
7 | import org.springframework.amqp.rabbit.connection.ConnectionFactory;
8 | import org.springframework.amqp.rabbit.core.RabbitTemplate;
9 | import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
10 | import org.springframework.beans.factory.annotation.Value;
11 | import org.springframework.context.annotation.Bean;
12 | import org.springframework.context.annotation.Configuration;
13 |
14 | import com.fasterxml.jackson.databind.ObjectMapper;
15 |
16 | @Configuration
17 | public class UserRegistrationConfig {
18 |
19 | @Value("${exchange-name}")
20 | String exchangeName;
21 |
22 | @Value("${queue}")
23 | String queue;
24 |
25 | @Value("${routingKey}")
26 | String routingKey;
27 |
28 | @Bean
29 | public TopicExchange exchange() {
30 | return new TopicExchange(exchangeName);
31 | }
32 |
33 | @Bean
34 | public Queue queue() {
35 | return new Queue(queue);
36 | }
37 |
38 | @Bean
39 | public Binding binding() {
40 | return BindingBuilder.bind(queue()).to(exchange()).with(routingKey);
41 | }
42 |
43 | @Bean
44 | public Jackson2JsonMessageConverter messageConverter() {
45 | ObjectMapper mapper = new ObjectMapper();
46 | return new Jackson2JsonMessageConverter(mapper);
47 | }
48 |
49 | @Bean
50 | public RabbitTemplate rabbitTemplate(final ConnectionFactory connectionFactory) {
51 | RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
52 | rabbitTemplate.setMessageConverter(messageConverter());
53 | return rabbitTemplate;
54 | }
55 |
56 | // @Bean
57 | // SimpleMessageListenerContainer container(ConnectionFactory connectionFactory,
58 | // MessageListenerAdapter listenerAdapter) {
59 | // SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
60 | // container.setConnectionFactory(connectionFactory);
61 | // container.setQueueNames(queue);
62 | // container.setMessageListener(listenerAdapter);
63 | // return container;
64 | // }
65 | //
66 | // @Bean
67 | // MessageListenerAdapter listenerAdapter(UserRegistrationNotification receiver) {
68 | // return new MessageListenerAdapter(receiver, "receiveMessage");
69 | // }
70 | //
71 | public String getExchangeName() {
72 | return exchangeName;
73 | }
74 |
75 | public String getQueue() {
76 | return queue;
77 | }
78 |
79 | public String getRoutingKey() {
80 | return routingKey;
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/backend/user-registration/src/main/java/com/tushar/usermanagement/exception/CustomResponse.java:
--------------------------------------------------------------------------------
1 | package com.tushar.usermanagement.exception;
2 |
3 | import java.time.LocalDateTime;
4 |
5 | import com.fasterxml.jackson.annotation.JsonFormat;
6 |
7 | public class CustomResponse {
8 |
9 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd hh:mm:ss")
10 | private LocalDateTime timestamp;
11 | private int status;
12 | private String message;
13 |
14 | public LocalDateTime getTimestamp() {
15 | return timestamp;
16 | }
17 |
18 | public void setTimestamp(LocalDateTime timestamp) {
19 | this.timestamp = timestamp;
20 | }
21 |
22 | public int getStatus() {
23 | return status;
24 | }
25 |
26 | public void setStatus(int status) {
27 | this.status = status;
28 | }
29 |
30 | public String getMessage() {
31 | return message;
32 | }
33 |
34 | public void setMessage(String message) {
35 | this.message = message;
36 | }
37 |
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/backend/user-registration/src/main/java/com/tushar/usermanagement/message/UserRegistrationNotification.java:
--------------------------------------------------------------------------------
1 | package com.tushar.usermanagement.message;
2 |
3 | public class UserRegistrationNotification {
4 |
5 | private String id;
6 | private String email;
7 | private String password;
8 |
9 | public UserRegistrationNotification(String id, String email, String password) {
10 | super();
11 | this.id = id;
12 | this.email = email;
13 | this.password = password;
14 | }
15 |
16 | public UserRegistrationNotification() {
17 | // TODO Auto-generated constructor stub
18 | }
19 |
20 | public String getId() {
21 | return id;
22 | }
23 |
24 | public void setId(String id) {
25 | this.id = id;
26 | }
27 |
28 | public String getEmail() {
29 | return email;
30 | }
31 |
32 | public void setEmail(String email) {
33 | this.email = email;
34 | }
35 |
36 | public String getPassword() {
37 | return password;
38 | }
39 |
40 | public void setPassword(String password) {
41 | this.password = password;
42 | }
43 |
44 | @Override
45 | public String toString() {
46 | return "UserRegistrationNotification [id=" + id + ", email=" + email + ", password=" + password + "]";
47 | }
48 |
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/backend/user-registration/src/main/java/com/tushar/usermanagement/model/User.java:
--------------------------------------------------------------------------------
1 | package com.tushar.usermanagement.model;
2 |
3 | import org.springframework.data.annotation.Id;
4 | import org.springframework.data.mongodb.core.index.Indexed;
5 | import org.springframework.data.mongodb.core.mapping.Document;
6 |
7 | @Document(collection="User")
8 | public class User {
9 |
10 | private String id;
11 |
12 | @Indexed(unique=true)
13 | private String email;
14 |
15 | private String password;
16 |
17 | public User(String email, String password) {
18 | this.email = email;
19 | this.password = password;
20 | }
21 |
22 | public String getId() {
23 | return id;
24 | }
25 |
26 | public void setId(String id) {
27 | this.id = id;
28 | }
29 |
30 | public String getEmail() {
31 | return email;
32 | }
33 |
34 | public void setEmail(String email) {
35 | this.email = email;
36 | }
37 |
38 | public String getPassword() {
39 | return password;
40 | }
41 |
42 | public void setPassword(String password) {
43 | this.password = password;
44 | }
45 |
46 |
47 |
48 |
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/backend/user-registration/src/main/java/com/tushar/usermanagement/repository/UserManagementRepository.java:
--------------------------------------------------------------------------------
1 | package com.tushar.usermanagement.repository;
2 |
3 | import org.springframework.data.mongodb.repository.MongoRepository;
4 |
5 | import com.tushar.usermanagement.model.User;
6 |
7 | public interface UserManagementRepository extends MongoRepository {
8 |
9 | User findByEmail(String email);
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/backend/user-registration/src/main/java/com/tushar/usermanagement/request/RegistrationRequest.java:
--------------------------------------------------------------------------------
1 | package com.tushar.usermanagement.request;
2 |
3 | import javax.validation.constraints.Email;
4 | import javax.validation.constraints.NotNull;
5 | import javax.validation.constraints.Size;
6 |
7 | import org.springframework.beans.factory.config.ConfigurableBeanFactory;
8 | import org.springframework.context.annotation.Scope;
9 |
10 | @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
11 | public class RegistrationRequest {
12 |
13 | @Email(message = "invalid email")
14 | @NotNull
15 | private String email;
16 |
17 | @NotNull
18 | @Size(min = 6, max = 10, message="password must be {min} to {max} characters long")
19 | private String password;
20 |
21 | public String getEmail() {
22 | return email;
23 | }
24 |
25 | public void setEmail(String email) {
26 | this.email = email;
27 | }
28 |
29 | public String getPassword() {
30 | return password;
31 | }
32 |
33 | public void setPassword(String password) {
34 | this.password = password;
35 | }
36 |
37 | @Override
38 | public String toString() {
39 | return "RegistrationRequest [email=" + email + ", password=" + password + "]";
40 | }
41 |
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/backend/user-registration/src/main/java/com/tushar/usermanagement/resources/UserManagementController.java:
--------------------------------------------------------------------------------
1 | package com.tushar.usermanagement.resources;
2 |
3 | import java.time.LocalDateTime;
4 | import java.util.Iterator;
5 | import java.util.List;
6 |
7 | import org.springframework.amqp.rabbit.core.RabbitTemplate;
8 | import org.springframework.beans.factory.annotation.Autowired;
9 | import org.springframework.http.HttpStatus;
10 | import org.springframework.http.ResponseEntity;
11 | import org.springframework.validation.ObjectError;
12 | import org.springframework.validation.annotation.Validated;
13 | import org.springframework.web.bind.MethodArgumentNotValidException;
14 | import org.springframework.web.bind.annotation.CrossOrigin;
15 | import org.springframework.web.bind.annotation.ExceptionHandler;
16 | import org.springframework.web.bind.annotation.GetMapping;
17 | import org.springframework.web.bind.annotation.PostMapping;
18 | import org.springframework.web.bind.annotation.RequestBody;
19 | import org.springframework.web.bind.annotation.ResponseStatus;
20 | import org.springframework.web.bind.annotation.RestController;
21 |
22 | import com.mongodb.DuplicateKeyException;
23 | import com.mongodb.MongoWriteException;
24 | import com.tushar.usermanagement.config.UserRegistrationConfig;
25 | import com.tushar.usermanagement.exception.CustomResponse;
26 | import com.tushar.usermanagement.message.UserRegistrationNotification;
27 | import com.tushar.usermanagement.model.User;
28 | import com.tushar.usermanagement.request.RegistrationRequest;
29 | import com.tushar.usermanagement.service.UserManagementService;
30 |
31 | @RestController
32 | public class UserManagementController {
33 |
34 | @Autowired
35 | private UserManagementService service;
36 |
37 | @Autowired
38 | private RabbitTemplate rabbitTemplate;
39 |
40 | @Autowired
41 | private UserRegistrationConfig userRegistrationConfig;
42 |
43 | @GetMapping("/all")
44 | public List getAllUsers() {
45 | return service.findAll();
46 | }
47 |
48 | @CrossOrigin
49 | @PostMapping("/user")
50 | public ResponseEntity registerUser(@Validated @RequestBody RegistrationRequest request) {
51 | System.out.println(request);
52 | User registeredUser = new User(request.getEmail(), request.getPassword());
53 | service.save(registeredUser);
54 | rabbitTemplate.convertAndSend(userRegistrationConfig.getExchangeName(), userRegistrationConfig.getRoutingKey(),
55 | new UserRegistrationNotification(registeredUser.getId(), registeredUser.getEmail(),
56 | registeredUser.getPassword()));
57 |
58 | CustomResponse success = new CustomResponse();
59 | success.setTimestamp(LocalDateTime.now());
60 | success.setMessage("Thanks for signing up. An email has been sent to " + request.getEmail());
61 | success.setStatus(HttpStatus.CREATED.value());
62 |
63 | return new ResponseEntity<>(success, HttpStatus.CREATED);
64 | }
65 |
66 | @ResponseStatus(value = HttpStatus.CONFLICT, reason = "duplicate email address")
67 | @ExceptionHandler(value = { MongoWriteException.class, DuplicateKeyException.class })
68 | public void duplicateEmailAddress() {
69 | }
70 |
71 | @ExceptionHandler(value = { MethodArgumentNotValidException.class })
72 | public ResponseEntity invalidInput(MethodArgumentNotValidException e) {
73 |
74 | StringBuffer buffer = new StringBuffer();
75 | for (ObjectError err : e.getBindingResult().getAllErrors()) {
76 | buffer.append(err.getDefaultMessage() + " , ");
77 | }
78 |
79 | CustomResponse errors = new CustomResponse();
80 | errors.setTimestamp(LocalDateTime.now());
81 | errors.setMessage(buffer.substring(0, buffer.length() - 2).toString());
82 | errors.setStatus(HttpStatus.PRECONDITION_FAILED.value());
83 |
84 | return new ResponseEntity<>(errors, HttpStatus.PRECONDITION_FAILED);
85 | }
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/backend/user-registration/src/main/java/com/tushar/usermanagement/service/UserManagementService.java:
--------------------------------------------------------------------------------
1 | package com.tushar.usermanagement.service;
2 |
3 | import com.tushar.usermanagement.repository.UserManagementRepository;
4 | import com.tushar.usermanagement.model.User;
5 | import java.util.List;
6 |
7 | import org.springframework.beans.factory.annotation.Autowired;
8 | import org.springframework.stereotype.Service;
9 |
10 | @Service
11 | public class UserManagementService {
12 |
13 | @Autowired
14 | private UserManagementRepository userManagementRepository;
15 |
16 | public void save(User user) {
17 | userManagementRepository.save(user);
18 | }
19 |
20 | public List findAll() {
21 | List user = userManagementRepository.findAll();
22 | System.out.println(user);
23 | return user;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/backend/user-registration/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | server.port=8080
2 | spring.application.name=user-service
3 | spring.data.mongodb.uri=mongodb://127.0.0.1:27017/admin
4 | logging.level.org.springframework.data.mongodb.core.MongoTemplate=DEBUG
5 | exchange-name=user-registration-exchange
6 | routingKey=user-registration-key
7 | queue=user-registration-queue
8 | spring.rabbitmq.port=5672
9 | logging.level.root=INFO
10 | logging.level.org.springframework=INFO
11 | eureka.client.service-url.default-zone=http://localhost:8761/eureka
12 |
--------------------------------------------------------------------------------
/backend/user-registration/src/module-info.java:
--------------------------------------------------------------------------------
1 | module user.registration {
2 | exports main.java.com.tushar.usermanagement;
3 | exports main.java.com.tushar.usermanagement.service;
4 | exports main.java.com.tushar.usermanagement.exception;
5 | exports main.java.com.tushar.usermanagement.request;
6 | exports main.java.com.tushar.usermanagement.repository;
7 | exports main.java.com.tushar.usermanagement.message;
8 | exports main.java.com.tushar.usermanagement.model;
9 | exports main.java.com.tushar.usermanagement.resources;
10 | exports main.java.com.tushar.usermanagement.config;
11 |
12 | requires com.fasterxml.jackson.databind;
13 | requires jackson.annotations;
14 | requires java.validation;
15 | requires mongo.java.driver;
16 | requires spring.amqp;
17 | requires spring.beans;
18 | requires spring.boot;
19 | requires spring.boot.autoconfigure;
20 | requires spring.context;
21 | requires spring.data.commons;
22 | requires spring.data.mongodb;
23 | requires spring.rabbit;
24 | requires spring.web;
25 | }
--------------------------------------------------------------------------------
/images/Error.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tushargoel86/user-registration-using-microservices/369f5d28113bdb6a44a3d9c0f145cc9ad49ad0c4/images/Error.PNG
--------------------------------------------------------------------------------
/images/Success.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tushargoel86/user-registration-using-microservices/369f5d28113bdb6a44a3d9c0f145cc9ad49ad0c4/images/Success.PNG
--------------------------------------------------------------------------------
/images/UserRegistrationForm.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tushargoel86/user-registration-using-microservices/369f5d28113bdb6a44a3d9c0f145cc9ad49ad0c4/images/UserRegistrationForm.PNG
--------------------------------------------------------------------------------
/images/diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tushargoel86/user-registration-using-microservices/369f5d28113bdb6a44a3d9c0f145cc9ad49ad0c4/images/diagram.png
--------------------------------------------------------------------------------
/ui/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "user-management",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "eslint-plugin-react-hooks": "^2.0.1",
7 | "react": "^16.9.0",
8 | "react-dom": "^16.9.0",
9 | "react-scripts": "3.1.1",
10 | "semantic-ui-css": "^2.4.1",
11 | "semantic-ui-react": "^0.88.0"
12 | },
13 | "scripts": {
14 | "start": "react-scripts start",
15 | "build": "react-scripts build",
16 | "test": "react-scripts test",
17 | "eject": "react-scripts eject"
18 | },
19 | "eslintConfig": {
20 | "extends": "react-app"
21 | },
22 | "browserslist": {
23 | "production": [
24 | ">0.2%",
25 | "not dead",
26 | "not op_mini all"
27 | ],
28 | "development": [
29 | "last 1 chrome version",
30 | "last 1 firefox version",
31 | "last 1 safari version"
32 | ]
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/ui/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/ui/src/App.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "semantic-ui-css/semantic.min.css";
3 | import UserRegistration from "./components/UserRegistration";
4 |
5 | function App() {
6 | return ;
7 | }
8 |
9 | export default App;
10 |
--------------------------------------------------------------------------------
/ui/src/components/Error.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Message } from "semantic-ui-react";
3 |
4 | export default function Error({ message }) {
5 | return (
6 |
7 | Error
8 |
9 | {message}
10 |
11 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/ui/src/components/Success.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Message } from "semantic-ui-react";
3 |
4 | export default function Success({ message }) {
5 | return (
6 |
7 | Success
8 |
9 | {message}
10 |
11 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/ui/src/components/UserRegistration.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { Form, Grid, Container, Button } from "semantic-ui-react";
3 | import Error from "./Error";
4 | import Success from "./Success";
5 |
6 | import { URL } from "../constants/Constants";
7 |
8 | export function registerEmail(email, password, setRes) {
9 | fetch(URL + "user", {
10 | method: "POST",
11 | headers: {
12 | "Content-Type": "application/json"
13 | },
14 | body: JSON.stringify({ email: email, password: password })
15 | })
16 | .then(resp => resp.json())
17 | .then(res => {
18 | if (res.status === 201) {
19 | setRes({ errorMessage: "", successMessage: res.message });
20 | } else {
21 | setRes({ successMessage: "", errorMessage: res.message });
22 | }
23 | })
24 | .catch(err => setRes({ successMessage: "", errorMessage: err.message }));
25 | }
26 |
27 | export default function UserRegistration() {
28 | const [email, setEmail] = useState("");
29 | const [password, setPassword] = useState("");
30 | const [res, setRes] = useState({ errorMessage: "", successMessage: "" });
31 |
32 | function handleSubmit(e) {
33 | e.preventDefault();
34 | registerEmail(email, password, setRes);
35 | }
36 |
37 | return (
38 |
39 |
40 |
41 |
42 | setEmail(e.target.value)}
47 | value={email}
48 | />
49 | setPassword(e.target.value)}
53 | value={password}
54 | type="password"
55 | />
56 |
57 |
58 | {res.errorMessage ? : null}
59 | {res.successMessage ? (
60 |
61 | ) : null}
62 |
63 |
64 |
65 |
66 | );
67 | }
68 |
69 |
--------------------------------------------------------------------------------
/ui/src/constants/Constants.js:
--------------------------------------------------------------------------------
1 | export const URL = 'http://localhost:8765/user-service/';
2 |
--------------------------------------------------------------------------------
/ui/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import App from "./App";
4 | import * as serviceWorker from "./serviceWorker";
5 |
6 | ReactDOM.render(, document.getElementById("root"));
7 |
8 | // If you want your app to work offline and load faster, you can change
9 | // unregister() to register() below. Note this comes with some pitfalls.
10 | // Learn more about service workers: https://bit.ly/CRA-PWA
11 | serviceWorker.unregister();
12 |
--------------------------------------------------------------------------------
/ui/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.1/8 is considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl)
104 | .then(response => {
105 | // Ensure service worker exists, and that we really are getting a JS file.
106 | const contentType = response.headers.get('content-type');
107 | if (
108 | response.status === 404 ||
109 | (contentType != null && contentType.indexOf('javascript') === -1)
110 | ) {
111 | // No service worker found. Probably a different app. Reload the page.
112 | navigator.serviceWorker.ready.then(registration => {
113 | registration.unregister().then(() => {
114 | window.location.reload();
115 | });
116 | });
117 | } else {
118 | // Service worker found. Proceed as normal.
119 | registerValidSW(swUrl, config);
120 | }
121 | })
122 | .catch(() => {
123 | console.log(
124 | 'No internet connection found. App is running in offline mode.'
125 | );
126 | });
127 | }
128 |
129 | export function unregister() {
130 | if ('serviceWorker' in navigator) {
131 | navigator.serviceWorker.ready.then(registration => {
132 | registration.unregister();
133 | });
134 | }
135 | }
136 |
--------------------------------------------------------------------------------