Skip to content

Commit

Permalink
📚implement OpenAPI-Swagger documentation across microservices
Browse files Browse the repository at this point in the history
  • Loading branch information
Adnane Miliari committed Nov 29, 2024
1 parent dc3e201 commit b6bd0fa
Show file tree
Hide file tree
Showing 19 changed files with 513 additions and 21 deletions.
20 changes: 0 additions & 20 deletions common/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,5 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-reporter-brave</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
</project>
17 changes: 17 additions & 0 deletions common/src/main/java/swagger/BaseController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package swagger;

import lombok.experimental.UtilityClass;

@UtilityClass
public class BaseController {
public static final String CUSTOMER_TAG = "Customer Management";
public static final String CUSTOMER_DESCRIPTION = "Operations about customers including orders and payments";
public static final String PRODUCT_TAG = "Product Management";
public static final String PRODUCT_DESCRIPTION = "Operations for managing products catalog and inventory";
public static final String ORDER_TAG = "Order Management";
public static final String ORDER_DESCRIPTION = "Operations for managing customer orders and order processing";
public static final String PAYMENT_TAG = "Payment Management";
public static final String PAYMENT_DESCRIPTION = "Operations for managing payments and transactions";
public static final String NOTIFICATION_TAG = "Notification Management";
public static final String NOTIFICATION_DESCRIPTION = "Operations for managing notifications and communication with customers";
}
40 changes: 40 additions & 0 deletions common/src/main/java/swagger/OpenAPIConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package swagger;

import io.swagger.v3.oas.models.ExternalDocumentation;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableConfigurationProperties(OpenAPIProperties.class)
public class OpenAPIConfig {

private final OpenAPIProperties properties;

public OpenAPIConfig(OpenAPIProperties properties) {
this.properties = properties;
}

@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title(properties.getTitle())
.version(properties.getVersion())
.description(properties.getDescription())
.contact(new Contact()
.name(properties.getContact().getName())
.email(properties.getContact().getEmail())
.url(properties.getContact().getUrl()))
.license(new License()
.name(properties.getLicense().getName())
.url(properties.getLicense().getUrl())))
.externalDocs(new ExternalDocumentation()
.description(properties.getExternalDocs().getDescription())
.url(properties.getExternalDocs().getUrl()));
}
}
38 changes: 38 additions & 0 deletions common/src/main/java/swagger/OpenAPIProperties.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package swagger;

import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "openapi")
@Getter @Setter
public class OpenAPIProperties {
private String title;
private String version;
private String description;
private Contact contact = new Contact();
private License license = new License();
private ExternalDocs externalDocs = new ExternalDocs();

@Getter
@Setter
public static class Contact {
private String name;
private String email;
private String url;
}

@Getter
@Setter
public static class License {
private String name;
private String url;
}

@Getter
@Setter
public static class ExternalDocs {
private String description;
private String url;
}
}
14 changes: 14 additions & 0 deletions common/src/main/resources/application-openapi.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
openapi:
title: Microservices API Documentation
version: 1.0
description: Documentation for all microservices endpoints
contact:
name: Adnane MILIARI
email: [email protected]
url: https://github.com/miliariadnane
license:
name: Apache 2.0
url: http://www.apache.org/licenses/LICENSE-2.0.html
external-docs:
description: Project Documentation
url: https://github.com/miliariadnane/demo-microservices
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package dev.nano.customer;

import lombok.experimental.UtilityClass;

@UtilityClass
public class CustomerConstant {
public static final String CUSTOMER_URI_REST_API = "/api/v1/customers";
public static final String CUSTOMER_NOT_FOUND = "Customer with ID %d not found";
Expand Down
118 changes: 117 additions & 1 deletion customer/src/main/java/dev/nano/customer/CustomerController.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,20 @@
import dev.nano.clients.order.OrderResponse;
import dev.nano.clients.payment.PaymentRequest;
import dev.nano.clients.payment.PaymentResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import swagger.BaseController;

import java.util.List;

Expand All @@ -18,23 +26,71 @@

@RestController
@RequestMapping(path = CUSTOMER_URI_REST_API)
@Tag(name = BaseController.CUSTOMER_TAG, description = BaseController.CUSTOMER_DESCRIPTION)
@AllArgsConstructor @Slf4j
public class CustomerController {

private final CustomerService customerService;

@GetMapping(path = "/{customerId}")
@Operation(
summary = "Get customer by ID",
description = "Retrieve a customer's details using their unique identifier"
)
@ApiResponses(value = {
@ApiResponse(
responseCode = "200",
description = "Customer found successfully",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = CustomerDTO.class)
)
),
@ApiResponse(responseCode = "404", description = "Customer not found"),
@ApiResponse(responseCode = "500", description = "Internal server error")
})
@GetMapping("/{customerId}")
public ResponseEntity<CustomerDTO> getCustomer(@PathVariable("customerId") Long customerId) {
log.info("Retrieving customer with ID: {}", customerId);
return ResponseEntity.ok(customerService.getCustomer(customerId));
}

@Operation(
summary = "Get all customers",
description = "Retrieve a list of all customers in the system"
)
@ApiResponses(value = {
@ApiResponse(
responseCode = "200",
description = "List of customers retrieved successfully",
content = @Content(
mediaType = "application/json",
array = @ArraySchema(schema = @Schema(implementation = CustomerDTO.class))
)
),
@ApiResponse(responseCode = "500", description = "Internal server error")
})
@GetMapping
public ResponseEntity<List<CustomerDTO>> getAllCustomers() {
log.info("Retrieving all customers");
return ResponseEntity.ok(customerService.getAllCustomers());
}

@Operation(
summary = "Create new customer",
description = "Register a new customer in the system with their details"
)
@ApiResponses(value = {
@ApiResponse(
responseCode = "201",
description = "Customer created successfully",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = CustomerDTO.class)
)
),
@ApiResponse(responseCode = "400", description = "Invalid input data"),
@ApiResponse(responseCode = "500", description = "Internal server error")
})
@PostMapping("/add")
public ResponseEntity<CustomerDTO> createCustomer(@Valid @RequestBody CustomerDTO customerDTO) {
log.info("Creating new customer: {}", customerDTO);
Expand All @@ -44,6 +100,23 @@ public ResponseEntity<CustomerDTO> createCustomer(@Valid @RequestBody CustomerDT
);
}

@Operation(
summary = "Update customer",
description = "Update an existing customer's information"
)
@ApiResponses(value = {
@ApiResponse(
responseCode = "200",
description = "Customer updated successfully",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = CustomerDTO.class)
)
),
@ApiResponse(responseCode = "400", description = "Invalid input data"),
@ApiResponse(responseCode = "404", description = "Customer not found"),
@ApiResponse(responseCode = "500", description = "Internal server error")
})
@PutMapping("/{customerId}")
public ResponseEntity<CustomerDTO> updateCustomer(
@PathVariable Long customerId,
Expand All @@ -52,13 +125,39 @@ public ResponseEntity<CustomerDTO> updateCustomer(
return ResponseEntity.ok(customerService.updateCustomer(customerId, customerDTO));
}

@Operation(
summary = "Delete customer",
description = "Remove a customer from the database"
)
@ApiResponses(value = {
@ApiResponse(responseCode = "204", description = "Customer deleted successfully"),
@ApiResponse(responseCode = "404", description = "Customer not found"),
@ApiResponse(responseCode = "500", description = "Internal server error")
})
@DeleteMapping("/{customerId}")
public ResponseEntity<Void> deleteCustomer(@PathVariable Long customerId) {
log.info("Deleting customer with ID: {}", customerId);
customerService.deleteCustomer(customerId);
return ResponseEntity.noContent().build();
}

@Operation(
summary = "Create customer order",
description = "Place a new order for an existing customer"
)
@ApiResponses(value = {
@ApiResponse(
responseCode = "201",
description = "Order created successfully",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = OrderResponse.class)
)
),
@ApiResponse(responseCode = "400", description = "Invalid order data"),
@ApiResponse(responseCode = "404", description = "Customer not found"),
@ApiResponse(responseCode = "500", description = "Internal server error")
})
@PostMapping("/orders")
public ResponseEntity<OrderResponse> customerOrders(@Valid @RequestBody OrderRequest orderRequest) {
log.info("Processing order for customer: {}", orderRequest);
Expand All @@ -68,6 +167,23 @@ public ResponseEntity<OrderResponse> customerOrders(@Valid @RequestBody OrderReq
);
}

@Operation(
summary = "Process customer payment",
description = "Process a payment for a customer's order"
)
@ApiResponses(value = {
@ApiResponse(
responseCode = "201",
description = "Payment processed successfully",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = PaymentResponse.class)
)
),
@ApiResponse(responseCode = "400", description = "Invalid payment data"),
@ApiResponse(responseCode = "404", description = "Customer or order not found"),
@ApiResponse(responseCode = "500", description = "Internal server error")
})
@PostMapping("/payment")
public ResponseEntity<PaymentResponse> customerPayment(@Valid @RequestBody PaymentRequest paymentRequest) {
log.info("Processing payment for customer: {}", paymentRequest);
Expand Down
6 changes: 6 additions & 0 deletions gateway/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-reporter-brave</artifactId>
</dependency>
<dependency>
<groupId>dev.nano</groupId>
<artifactId>feign-clients</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,18 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.annotation.PropertySources;

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(
basePackages = "dev.nano.clients"
)
@PropertySources({
@PropertySource("classpath:clients-${spring.profiles.active}.properties")
})
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
Expand Down
2 changes: 2 additions & 0 deletions gateway/src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#Server
server:
port: 8006

#Management
management:
endpoints:
Expand All @@ -10,6 +11,7 @@ management:
endpoint:
health:
show-details: always

#Spring
spring:
application:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package dev.nano.notification;

import lombok.experimental.UtilityClass;

@UtilityClass
public class NotificationConstant {
public static final String NOTIFICATION_URI_REST_API = "/api/v1/notifications";
public static final String NOTIFICATION_NOT_FOUND = "Notification with ID %d not found";
Expand Down
Loading

0 comments on commit b6bd0fa

Please sign in to comment.