MyWorld project - Yash-777/MyWorld GitHub Wiki

Spring Boot modular project structure for your MyWorld project, following a modular approach with myworld-api, myworld-common, myworld-db, myworld-security, and other modules.

Project Structure & Multi module project using eclipse

πŸ“Œ File -> new -> Project -> Maven project - New Maven Project select Create a simple project, which will only create a pom file and skip all other unnecessary folders for multi module project.

1️⃣ Project Name and Workspace Location: C:\Yashwanth\WorkSetup\WorkSpace\myworld. Here, I have already created a folder named as myworld where we will create our multi module project which I have selected as workspace location.

2️⃣ Configure Project: [Group Id:com.github.yash777, Artifact Id: myworld, packaging: pom]

<project xmlns="http://maven.apache.org/POM/4.0.0" 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  
  <groupId>com.github.yash777</groupId>
  <artifactId>myworld</artifactId>
  <version>0.0.1-SNAPSHOT</version>

  <packaging>pom</packaging>

  <name>MyWorld modular projec</name>
  <description>Spring Boot modular project structure for your MyWorld project</description>
</project>

Description of Each Module

  • myworld-api β†’ Contains API interfaces and DTOs (Data Transfer Objects).
  • myworld-common β†’ Contains utility classes, constants, and common logic.
  • myworld-db β†’ Manages database entities, repositories, and configurations.
  • myworld-security β†’ Handles authentication, authorization, and security logic.
  • myworld-service β†’ Implements business logic and interacts with repositories.
  • myworld-web β†’ Exposes REST APIs and serves as the entry point for requests.
myworld/
│── myworld-api/
β”‚   β”œβ”€β”€ src/main/java/com/github/yash777/myworld/api/
β”‚   β”œβ”€β”€ src/main/resources/
β”‚   β”œβ”€β”€ pom.xml
β”‚
│── myworld-common/
β”‚   β”œβ”€β”€ src/main/java/com/github/yash777/myworld/common/
β”‚   β”œβ”€β”€ src/main/resources/
β”‚   β”œβ”€β”€ pom.xml
β”‚
│── myworld-db/
β”‚   β”œβ”€β”€ src/main/java/com/github/yash777/myworld/db/
β”‚   β”œβ”€β”€ src/main/resources/
β”‚   β”œβ”€β”€ pom.xml
β”‚
│── myworld-security/
β”‚   β”œβ”€β”€ src/main/java/com/github/yash777/myworld/security/
β”‚   β”œβ”€β”€ src/main/resources/
β”‚   β”œβ”€β”€ pom.xml
β”‚
│── myworld-service/
β”‚   β”œβ”€β”€ src/main/java/com/github/yash777/myworld/service/
β”‚   β”œβ”€β”€ src/main/resources/
β”‚   β”œβ”€β”€ pom.xml
β”‚
│── myworld-web/
β”‚   β”œβ”€β”€ src/main/java/com/github/yash777/myworld/web/
β”‚   β”œβ”€β”€ src/main/resources/
β”‚   β”œβ”€β”€ pom.xml
β”‚
│── pom.xml (Parent POM)

πŸ“Œ We are done with our parent, now let’s add child module. Right click on the project myworld -> new -> project -> Maven Module

1️⃣ Module Name: myworld-api

2️⃣ Configure Project: [Group Id:com.github.yash777, Artifact Id: myworld-api, packaging: jar/war, Name: API Endpoints]


Here's a clear, structured explanation showing how only the main module's application.properties is automatically loaded by Spring Boot β€” and how you can manually load submodule-specific configuration files (like log-config.properties) from a child module (myapp-common)

βœ… Multi-Module Maven Project Structure

myapp-parent/
β”‚
β”œβ”€β”€ myapp-common/                         # Shared module with common utilities
β”‚   β”œβ”€β”€ pom.xml
β”‚   β”œβ”€β”€ src/main/java/com/example/common/
β”‚   β”‚   └── LogConfig.java
β”‚   β”‚   └── LogProperties.java
β”‚   └── src/main/resources/
β”‚       β”œβ”€β”€ application.properties        ❌ (NOT auto-loaded)
β”‚       └── log-config.properties         βœ… Custom properties file for logging
β”‚
β”œβ”€β”€ myapp-application/                    # Main application module
β”‚   β”œβ”€β”€ pom.xml
β”‚   β”œβ”€β”€ src/main/java/com/example/app/
β”‚   β”‚   └── MyAppApplication.java         βœ… Main class
β”‚   └── src/main/resources/
β”‚       └── application.properties        βœ… Loaded automatically by Spring Boot
β”‚
└── pom.xml                               # Parent POM

🧠 Spring Boot Behavior (Important!)

βœ… Only one application.properties is automatically loaded β€” the one in the module where the @SpringBootApplication main class lives (myapp-application).

❌ application.properties in myapp-common will be ignored unless you explicitly load it.

βœ… If you want to load module-specific configs (like for logging), create a custom-named properties file like log-config.properties or application-log.properties.

βœ… Solution: Load Submodule-Specific Config Manually

πŸ”Έ log-config.properties (inside myapp-common/src/main/resources/):

log.file-path=D:/temp
log.file-name=common-module.log
log.pattern=%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
log.max-size-mb=10
log.max-backups=5
log.file-extension=gz

⚠ Use hyphen-case in properties if you're using @ConfigurationProperties, unless you bind with relaxed naming. Spring Boot will bind log.file-path to filePath.

πŸ”Έ LogProperties.java (in myapp-common)

package com.example.common;

// Loads logging configuration from a custom properties file in the common module.
@lombok.Data @lombok.ToString
@org.springframework.stereotype.Component

@EnableConfigurationProperties(LogProperties.class) // Make sure to enable configuration binding - In your main class or config
@org.springframework.boot.context.properties.ConfigurationProperties(prefix = "log")
@org.springframework.context.annotation.PropertySource("classpath:log-config.properties")  // βœ… Load manually
public class LogProperties {
    private String filePath, fileName, pattern;
    private long maxSizeMb;
    private int maxBackups;
    private String fileExtension = "gz"; // default to gz if not set
}

πŸ”Έ LogConfig.java (in myapp-common)

@Configuration @Slf4j
public class LogConfig {

    @Autowired
    private LogProperties logProperties;

    @PostConstruct
    public void setupLogger() {
        log.info("πŸ“‚ Loaded Logging Properties from log-config/application-log.properties: {}", logProperties);
        System.out.println("LogProperties: " + logProperties);

        LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();

        // Setup the encoder
        PatternLayoutEncoder encoder = new PatternLayoutEncoder();
        encoder.setContext(context);
        encoder.setPattern(logProperties.getPattern());
        encoder.start();

        // Setup the rolling file appender
        RollingFileAppender<ILoggingEvent> fileAppender = new RollingFileAppender<>();
        fileAppender.setContext(context);
        fileAppender.setFile(logProperties.getFilePath() + "/" + logProperties.getFileName());

        fileAppender.setAppend(true); // βœ… Ensure it appends to the existing log file

        fileAppender.setEncoder(encoder);

        // Determine extension
        String extension = logProperties.getFileExtension().toLowerCase().startsWith(".")
                ? logProperties.getFileExtension()
                : "." + logProperties.getFileExtension();
        
        // Rolling policy
        FixedWindowRollingPolicy rollingPolicy = new FixedWindowRollingPolicy();
        rollingPolicy.setContext(context);
        rollingPolicy.setParent(fileAppender);
        rollingPolicy.setFileNamePattern(logProperties.getFilePath() + "/" + logProperties.getFileName() + ".%i" + extension); // .gz or .zip
        rollingPolicy.setMinIndex(1);
        rollingPolicy.setMaxIndex(logProperties.getMaxBackups());
        rollingPolicy.start();

        // Triggering policy
        SizeBasedTriggeringPolicy<ILoggingEvent> triggeringPolicy = new SizeBasedTriggeringPolicy<>();
        triggeringPolicy.setContext(context);
        triggeringPolicy.setMaxFileSize(new FileSize(logProperties.getMaxSizeMb() * 1024 * 1024)); // Convert MB to bytes
        triggeringPolicy.start();

        fileAppender.setRollingPolicy(rollingPolicy);
        fileAppender.setTriggeringPolicy(triggeringPolicy);
        fileAppender.start();

        // Register this appender with root logger
        Logger rootLogger = context.getLogger(Logger.ROOT_LOGGER_NAME);
        rootLogger.addAppender(fileAppender);
    }
}

By default, RollingFileAppender does not append unless you explicitly set:

fileAppender.setAppend(true);

Without this, it overwrites the existing file every time your app starts.


πŸ“Œ Sample Flow

1️⃣ myworld-api (API Layer)

package com.github.yash777.myworld.api;

public interface UserService {
    String getUserInfo(Long userId);
}

2️⃣ myworld-common (Utility Layer)

package com.github.yash777.myworld.common;

public class Constants {
    public static final String USER_NOT_FOUND = "User not found!";
}

3️⃣ myworld-db (Repository Layer)

package com.github.yash777.myworld.db.entity;

import jakarta.persistence.*;

@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
}
package com.github.yash777.myworld.db.repository;

import com.github.yash777.myworld.db.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
}

4️⃣ myworld-service (Service Layer)

package com.github.yash777.myworld.service;

import com.github.yash777.myworld.api.UserService;
import com.github.yash777.myworld.db.entity.User;
import com.github.yash777.myworld.db.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Optional;

@Service
public class UserServiceImpl implements UserService {
    @Autowired private UserRepository userRepository;

    @Override
    public String getUserInfo(Long userId) {
        Optional<User> user = userRepository.findById(userId);
        return user.map(User::getName).orElse("User Not Found");
    }
}

5️⃣ myworld-security (Security Layer)

package com.github.yash777.myworld.security;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
            .formLogin()
            .and()
            .httpBasic();
    }
}

6️⃣ myworld-web (Controller Layer)

package com.github.yash777.myworld.web;

import com.github.yash777.myworld.api.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/users")
public class UserController {
    @Autowired private UserService userService;

    @GetMapping("/{id}")
    public String getUser(@PathVariable Long id) {
        return userService.getUserInfo(id);
    }
}

πŸ“Œ Parent pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.github.yash777.myworld</groupId>
    <artifactId>myworld</artifactId>
    <version>1.0.0</version>
    <packaging>pom</packaging>

    <modules>
        <module>myworld-api</module>
        <module>myworld-common</module>
        <module>myworld-db</module>
        <module>myworld-security</module>
        <module>myworld-service</module>
        <module>myworld-web</module>
    </modules>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>3.1.0</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <properties>
        <java.version>17</java.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

πŸ“Œ How to Run the Project Start the Database (if using PostgreSQL, MySQL, etc.).

Build the Project:

mvn clean install

Run the Web Module:

cd myworld-web
mvn spring-boot:run

Test the API in Browser/Postman:

GET http://localhost:8080/users/1
⚠️ **GitHub.com Fallback** ⚠️