Skip to content

Commit b6bd0fa

Browse files
author
Adnane Miliari
committed
📚implement OpenAPI-Swagger documentation across microservices
1 parent dc3e201 commit b6bd0fa

File tree

19 files changed

+513
-21
lines changed

19 files changed

+513
-21
lines changed

common/pom.xml

-20
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,5 @@
1414
<groupId>org.springframework.boot</groupId>
1515
<artifactId>spring-boot-starter-web</artifactId>
1616
</dependency>
17-
<dependency>
18-
<groupId>org.springframework.boot</groupId>
19-
<artifactId>spring-boot-starter-data-jpa</artifactId>
20-
</dependency>
21-
<dependency>
22-
<groupId>org.springframework.cloud</groupId>
23-
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
24-
</dependency>
25-
<dependency>
26-
<groupId>io.micrometer</groupId>
27-
<artifactId>micrometer-tracing-bridge-brave</artifactId>
28-
</dependency>
29-
<dependency>
30-
<groupId>io.zipkin.reporter2</groupId>
31-
<artifactId>zipkin-reporter-brave</artifactId>
32-
</dependency>
33-
<dependency>
34-
<groupId>org.springframework.boot</groupId>
35-
<artifactId>spring-boot-starter-actuator</artifactId>
36-
</dependency>
3717
</dependencies>
3818
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package swagger;
2+
3+
import lombok.experimental.UtilityClass;
4+
5+
@UtilityClass
6+
public class BaseController {
7+
public static final String CUSTOMER_TAG = "Customer Management";
8+
public static final String CUSTOMER_DESCRIPTION = "Operations about customers including orders and payments";
9+
public static final String PRODUCT_TAG = "Product Management";
10+
public static final String PRODUCT_DESCRIPTION = "Operations for managing products catalog and inventory";
11+
public static final String ORDER_TAG = "Order Management";
12+
public static final String ORDER_DESCRIPTION = "Operations for managing customer orders and order processing";
13+
public static final String PAYMENT_TAG = "Payment Management";
14+
public static final String PAYMENT_DESCRIPTION = "Operations for managing payments and transactions";
15+
public static final String NOTIFICATION_TAG = "Notification Management";
16+
public static final String NOTIFICATION_DESCRIPTION = "Operations for managing notifications and communication with customers";
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package swagger;
2+
3+
import io.swagger.v3.oas.models.ExternalDocumentation;
4+
import io.swagger.v3.oas.models.OpenAPI;
5+
import io.swagger.v3.oas.models.info.Contact;
6+
import io.swagger.v3.oas.models.info.Info;
7+
import io.swagger.v3.oas.models.info.License;
8+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
9+
import org.springframework.context.annotation.Bean;
10+
import org.springframework.context.annotation.Configuration;
11+
12+
@Configuration
13+
@EnableConfigurationProperties(OpenAPIProperties.class)
14+
public class OpenAPIConfig {
15+
16+
private final OpenAPIProperties properties;
17+
18+
public OpenAPIConfig(OpenAPIProperties properties) {
19+
this.properties = properties;
20+
}
21+
22+
@Bean
23+
public OpenAPI customOpenAPI() {
24+
return new OpenAPI()
25+
.info(new Info()
26+
.title(properties.getTitle())
27+
.version(properties.getVersion())
28+
.description(properties.getDescription())
29+
.contact(new Contact()
30+
.name(properties.getContact().getName())
31+
.email(properties.getContact().getEmail())
32+
.url(properties.getContact().getUrl()))
33+
.license(new License()
34+
.name(properties.getLicense().getName())
35+
.url(properties.getLicense().getUrl())))
36+
.externalDocs(new ExternalDocumentation()
37+
.description(properties.getExternalDocs().getDescription())
38+
.url(properties.getExternalDocs().getUrl()));
39+
}
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package swagger;
2+
3+
import lombok.Getter;
4+
import lombok.Setter;
5+
import org.springframework.boot.context.properties.ConfigurationProperties;
6+
7+
@ConfigurationProperties(prefix = "openapi")
8+
@Getter @Setter
9+
public class OpenAPIProperties {
10+
private String title;
11+
private String version;
12+
private String description;
13+
private Contact contact = new Contact();
14+
private License license = new License();
15+
private ExternalDocs externalDocs = new ExternalDocs();
16+
17+
@Getter
18+
@Setter
19+
public static class Contact {
20+
private String name;
21+
private String email;
22+
private String url;
23+
}
24+
25+
@Getter
26+
@Setter
27+
public static class License {
28+
private String name;
29+
private String url;
30+
}
31+
32+
@Getter
33+
@Setter
34+
public static class ExternalDocs {
35+
private String description;
36+
private String url;
37+
}
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
openapi:
2+
title: Microservices API Documentation
3+
version: 1.0
4+
description: Documentation for all microservices endpoints
5+
contact:
6+
name: Adnane MILIARI
7+
8+
url: https://github.com/miliariadnane
9+
license:
10+
name: Apache 2.0
11+
url: http://www.apache.org/licenses/LICENSE-2.0.html
12+
external-docs:
13+
description: Project Documentation
14+
url: https://github.com/miliariadnane/demo-microservices

customer/src/main/java/dev/nano/customer/CustomerConstant.java

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package dev.nano.customer;
22

3+
import lombok.experimental.UtilityClass;
4+
5+
@UtilityClass
36
public class CustomerConstant {
47
public static final String CUSTOMER_URI_REST_API = "/api/v1/customers";
58
public static final String CUSTOMER_NOT_FOUND = "Customer with ID %d not found";

customer/src/main/java/dev/nano/customer/CustomerController.java

+117-1
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,20 @@
44
import dev.nano.clients.order.OrderResponse;
55
import dev.nano.clients.payment.PaymentRequest;
66
import dev.nano.clients.payment.PaymentResponse;
7+
import io.swagger.v3.oas.annotations.Operation;
8+
import io.swagger.v3.oas.annotations.media.ArraySchema;
9+
import io.swagger.v3.oas.annotations.media.Content;
10+
import io.swagger.v3.oas.annotations.media.Schema;
11+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
12+
import io.swagger.v3.oas.annotations.responses.ApiResponses;
13+
import io.swagger.v3.oas.annotations.tags.Tag;
714
import jakarta.validation.Valid;
815
import lombok.AllArgsConstructor;
916
import lombok.extern.slf4j.Slf4j;
1017
import org.springframework.http.HttpStatus;
1118
import org.springframework.http.ResponseEntity;
1219
import org.springframework.web.bind.annotation.*;
20+
import swagger.BaseController;
1321

1422
import java.util.List;
1523

@@ -18,23 +26,71 @@
1826

1927
@RestController
2028
@RequestMapping(path = CUSTOMER_URI_REST_API)
29+
@Tag(name = BaseController.CUSTOMER_TAG, description = BaseController.CUSTOMER_DESCRIPTION)
2130
@AllArgsConstructor @Slf4j
2231
public class CustomerController {
2332

2433
private final CustomerService customerService;
2534

26-
@GetMapping(path = "/{customerId}")
35+
@Operation(
36+
summary = "Get customer by ID",
37+
description = "Retrieve a customer's details using their unique identifier"
38+
)
39+
@ApiResponses(value = {
40+
@ApiResponse(
41+
responseCode = "200",
42+
description = "Customer found successfully",
43+
content = @Content(
44+
mediaType = "application/json",
45+
schema = @Schema(implementation = CustomerDTO.class)
46+
)
47+
),
48+
@ApiResponse(responseCode = "404", description = "Customer not found"),
49+
@ApiResponse(responseCode = "500", description = "Internal server error")
50+
})
51+
@GetMapping("/{customerId}")
2752
public ResponseEntity<CustomerDTO> getCustomer(@PathVariable("customerId") Long customerId) {
2853
log.info("Retrieving customer with ID: {}", customerId);
2954
return ResponseEntity.ok(customerService.getCustomer(customerId));
3055
}
3156

57+
@Operation(
58+
summary = "Get all customers",
59+
description = "Retrieve a list of all customers in the system"
60+
)
61+
@ApiResponses(value = {
62+
@ApiResponse(
63+
responseCode = "200",
64+
description = "List of customers retrieved successfully",
65+
content = @Content(
66+
mediaType = "application/json",
67+
array = @ArraySchema(schema = @Schema(implementation = CustomerDTO.class))
68+
)
69+
),
70+
@ApiResponse(responseCode = "500", description = "Internal server error")
71+
})
3272
@GetMapping
3373
public ResponseEntity<List<CustomerDTO>> getAllCustomers() {
3474
log.info("Retrieving all customers");
3575
return ResponseEntity.ok(customerService.getAllCustomers());
3676
}
3777

78+
@Operation(
79+
summary = "Create new customer",
80+
description = "Register a new customer in the system with their details"
81+
)
82+
@ApiResponses(value = {
83+
@ApiResponse(
84+
responseCode = "201",
85+
description = "Customer created successfully",
86+
content = @Content(
87+
mediaType = "application/json",
88+
schema = @Schema(implementation = CustomerDTO.class)
89+
)
90+
),
91+
@ApiResponse(responseCode = "400", description = "Invalid input data"),
92+
@ApiResponse(responseCode = "500", description = "Internal server error")
93+
})
3894
@PostMapping("/add")
3995
public ResponseEntity<CustomerDTO> createCustomer(@Valid @RequestBody CustomerDTO customerDTO) {
4096
log.info("Creating new customer: {}", customerDTO);
@@ -44,6 +100,23 @@ public ResponseEntity<CustomerDTO> createCustomer(@Valid @RequestBody CustomerDT
44100
);
45101
}
46102

103+
@Operation(
104+
summary = "Update customer",
105+
description = "Update an existing customer's information"
106+
)
107+
@ApiResponses(value = {
108+
@ApiResponse(
109+
responseCode = "200",
110+
description = "Customer updated successfully",
111+
content = @Content(
112+
mediaType = "application/json",
113+
schema = @Schema(implementation = CustomerDTO.class)
114+
)
115+
),
116+
@ApiResponse(responseCode = "400", description = "Invalid input data"),
117+
@ApiResponse(responseCode = "404", description = "Customer not found"),
118+
@ApiResponse(responseCode = "500", description = "Internal server error")
119+
})
47120
@PutMapping("/{customerId}")
48121
public ResponseEntity<CustomerDTO> updateCustomer(
49122
@PathVariable Long customerId,
@@ -52,13 +125,39 @@ public ResponseEntity<CustomerDTO> updateCustomer(
52125
return ResponseEntity.ok(customerService.updateCustomer(customerId, customerDTO));
53126
}
54127

128+
@Operation(
129+
summary = "Delete customer",
130+
description = "Remove a customer from the database"
131+
)
132+
@ApiResponses(value = {
133+
@ApiResponse(responseCode = "204", description = "Customer deleted successfully"),
134+
@ApiResponse(responseCode = "404", description = "Customer not found"),
135+
@ApiResponse(responseCode = "500", description = "Internal server error")
136+
})
55137
@DeleteMapping("/{customerId}")
56138
public ResponseEntity<Void> deleteCustomer(@PathVariable Long customerId) {
57139
log.info("Deleting customer with ID: {}", customerId);
58140
customerService.deleteCustomer(customerId);
59141
return ResponseEntity.noContent().build();
60142
}
61143

144+
@Operation(
145+
summary = "Create customer order",
146+
description = "Place a new order for an existing customer"
147+
)
148+
@ApiResponses(value = {
149+
@ApiResponse(
150+
responseCode = "201",
151+
description = "Order created successfully",
152+
content = @Content(
153+
mediaType = "application/json",
154+
schema = @Schema(implementation = OrderResponse.class)
155+
)
156+
),
157+
@ApiResponse(responseCode = "400", description = "Invalid order data"),
158+
@ApiResponse(responseCode = "404", description = "Customer not found"),
159+
@ApiResponse(responseCode = "500", description = "Internal server error")
160+
})
62161
@PostMapping("/orders")
63162
public ResponseEntity<OrderResponse> customerOrders(@Valid @RequestBody OrderRequest orderRequest) {
64163
log.info("Processing order for customer: {}", orderRequest);
@@ -68,6 +167,23 @@ public ResponseEntity<OrderResponse> customerOrders(@Valid @RequestBody OrderReq
68167
);
69168
}
70169

170+
@Operation(
171+
summary = "Process customer payment",
172+
description = "Process a payment for a customer's order"
173+
)
174+
@ApiResponses(value = {
175+
@ApiResponse(
176+
responseCode = "201",
177+
description = "Payment processed successfully",
178+
content = @Content(
179+
mediaType = "application/json",
180+
schema = @Schema(implementation = PaymentResponse.class)
181+
)
182+
),
183+
@ApiResponse(responseCode = "400", description = "Invalid payment data"),
184+
@ApiResponse(responseCode = "404", description = "Customer or order not found"),
185+
@ApiResponse(responseCode = "500", description = "Internal server error")
186+
})
71187
@PostMapping("/payment")
72188
public ResponseEntity<PaymentResponse> customerPayment(@Valid @RequestBody PaymentRequest paymentRequest) {
73189
log.info("Processing payment for customer: {}", paymentRequest);

gateway/pom.xml

+6
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@
2828
<groupId>io.zipkin.reporter2</groupId>
2929
<artifactId>zipkin-reporter-brave</artifactId>
3030
</dependency>
31+
<dependency>
32+
<groupId>dev.nano</groupId>
33+
<artifactId>feign-clients</artifactId>
34+
<version>1.0-SNAPSHOT</version>
35+
<scope>compile</scope>
36+
</dependency>
3137
</dependencies>
3238
<build>
3339
<plugins>

gateway/src/main/java/dev/nano/gateway/GatewayApplication.java

+9
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,18 @@
33
import org.springframework.boot.SpringApplication;
44
import org.springframework.boot.autoconfigure.SpringBootApplication;
55
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
6+
import org.springframework.cloud.openfeign.EnableFeignClients;
7+
import org.springframework.context.annotation.PropertySource;
8+
import org.springframework.context.annotation.PropertySources;
69

710
@SpringBootApplication
811
@EnableDiscoveryClient
12+
@EnableFeignClients(
13+
basePackages = "dev.nano.clients"
14+
)
15+
@PropertySources({
16+
@PropertySource("classpath:clients-${spring.profiles.active}.properties")
17+
})
918
public class GatewayApplication {
1019
public static void main(String[] args) {
1120
SpringApplication.run(GatewayApplication.class, args);

gateway/src/main/resources/application.yml

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#Server
22
server:
33
port: 8006
4+
45
#Management
56
management:
67
endpoints:
@@ -10,6 +11,7 @@ management:
1011
endpoint:
1112
health:
1213
show-details: always
14+
1315
#Spring
1416
spring:
1517
application:

notification/src/main/java/dev/nano/notification/NotificationConstant.java

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package dev.nano.notification;
22

3+
import lombok.experimental.UtilityClass;
4+
5+
@UtilityClass
36
public class NotificationConstant {
47
public static final String NOTIFICATION_URI_REST_API = "/api/v1/notifications";
58
public static final String NOTIFICATION_NOT_FOUND = "Notification with ID %d not found";

0 commit comments

Comments
 (0)