Your First Application
Build a complete Sproogy application from scratch in under 10 minutes.
What We'll Build
A simple user management application with:
- Dependency injection
- Configuration management
- A service layer with business logic
- Console output (no socket server yet)
This will demonstrate Sproogy's core IoC/DI features before we add socket communication.
Step 1: Project Setup
Create a new Gradle project:
mkdir my-first-sproogy-app
cd my-first-sproogy-app
gradle init --type java-application
Or create manually with this structure:
my-first-sproogy-app/
├── src/
│ └── main/
│ ├── java/
│ │ └── com/
│ │ └── example/
│ └── resources/
│ └── application.yml
├── .env
└── build.gradle
Step 2: Add Sproogy Dependencies
build.gradle:
plugins {
id 'java'
id 'application'
}
group = 'com.example'
version = '1.0.0'
sourceCompatibility = '11'
repositories {
mavenCentral()
mavenLocal()
}
dependencies {
implementation 'com.sproogy:core:1.0.0'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.2'
implementation 'io.github.cdimascio:dotenv-java:3.0.0'
implementation 'org.slf4j:slf4j-simple:2.0.7'
}
application {
mainClass = 'com.example.Main'
}
Step 3: Create Configuration Files
.env:
APP_NAME=My First Sproogy App
APP_VERSION=1.0.0
MAX_USERS=100
src/main/resources/application.yml:
application:
features:
enableNotifications: true
enableAuditLog: false
limits:
maxLoginAttempts: 3
Step 4: Create Domain Model
src/main/java/com/example/model/User.java:
package com.example.model;
public class User {
private Long id;
private String username;
private String email;
private boolean active;
public User() {}
public User(Long id, String username, String email) {
this.id = id;
this.username = username;
this.email = email;
this.active = true;
}
// Getters and setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public boolean isActive() { return active; }
public void setActive(boolean active) { this.active = active; }
@Override
public String toString() {
return "User{id=" + id + ", username='" + username + "', email='" + email + "', active=" + active + "}";
}
}
Step 5: Create Repository Layer
src/main/java/com/example/repository/UserRepository.java:
package com.example.repository;
import com.example.model.User;
import com.sproogy.annotations.Component;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
@Component
public class UserRepository {
private final Map<Long, User> users = new ConcurrentHashMap<>();
private final AtomicLong idGenerator = new AtomicLong(1);
public User save(User user) {
if (user.getId() == null) {
user.setId(idGenerator.getAndIncrement());
}
users.put(user.getId(), user);
return user;
}
public Optional<User> findById(Long id) {
return Optional.ofNullable(users.get(id));
}
public List<User> findAll() {
return new ArrayList<>(users.values());
}
public Optional<User> findByUsername(String username) {
return users.values().stream()
.filter(u -> u.getUsername().equals(username))
.findFirst();
}
public void deleteById(Long id) {
users.remove(id);
}
public long count() {
return users.size();
}
}
Step 6: Create Service Layer
src/main/java/com/example/service/UserService.java:
package com.example.service;
import com.example.model.User;
import com.example.repository.UserRepository;
import com.sproogy.annotations.Autowired;
import com.sproogy.annotations.Env;
import com.sproogy.annotations.Service;
import com.sproogy.annotations.Value;
import java.util.List;
@Service
public class UserService {
private final UserRepository userRepository;
@Env("MAX_USERS")
private int maxUsers;
@Value("application.features.enableNotifications")
private boolean notificationsEnabled;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User createUser(String username, String email) {
// Business logic: Check user limit
if (userRepository.count() >= maxUsers) {
throw new IllegalStateException("Maximum user limit reached: " + maxUsers);
}
// Business logic: Validate unique username
if (userRepository.findByUsername(username).isPresent()) {
throw new IllegalArgumentException("Username already exists: " + username);
}
User user = new User(null, username, email);
user = userRepository.save(user);
if (notificationsEnabled) {
System.out.println("[NOTIFICATION] Welcome email sent to: " + email);
}
return user;
}
public User findById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("User not found: " + id));
}
public List<User> findAll() {
return userRepository.findAll();
}
public void deactivateUser(Long id) {
User user = findById(id);
user.setActive(false);
userRepository.save(user);
if (notificationsEnabled) {
System.out.println("[NOTIFICATION] Account deactivated for: " + user.getEmail());
}
}
public long getUserCount() {
return userRepository.count();
}
}
Step 7: Create Application Runner
src/main/java/com/example/AppRunner.java:
package com.example;
import com.example.service.UserService;
import com.sproogy.annotations.Autowired;
import com.sproogy.annotations.Component;
import com.sproogy.annotations.Env;
import com.sproogy.annotations.Value;
@Component
public class AppRunner {
@Autowired
private UserService userService;
@Env("APP_NAME")
private String appName;
@Env("APP_VERSION")
private String appVersion;
@Value("application.limits.maxLoginAttempts")
private int maxLoginAttempts;
public void run() {
System.out.println("=".repeat(50));
System.out.println(appName + " v" + appVersion);
System.out.println("Max Login Attempts: " + maxLoginAttempts);
System.out.println("=".repeat(50));
System.out.println();
// Create users
System.out.println("Creating users...");
var alice = userService.createUser("alice", "alice@example.com");
var bob = userService.createUser("bob", "bob@example.com");
var charlie = userService.createUser("charlie", "charlie@example.com");
System.out.println("✓ Created: " + alice);
System.out.println("✓ Created: " + bob);
System.out.println("✓ Created: " + charlie);
System.out.println();
// List all users
System.out.println("All users:");
userService.findAll().forEach(user -> System.out.println(" - " + user));
System.out.println("Total users: " + userService.getUserCount());
System.out.println();
// Deactivate a user
System.out.println("Deactivating Bob...");
userService.deactivateUser(bob.getId());
System.out.println("✓ Bob deactivated: " + userService.findById(bob.getId()));
System.out.println();
// Test validation
System.out.println("Testing validation...");
try {
userService.createUser("alice", "alice2@example.com");
} catch (IllegalArgumentException e) {
System.out.println("✓ Validation worked: " + e.getMessage());
}
}
}
Step 8: Create Main Class
src/main/java/com/example/Main.java:
package com.example;
import com.sproogy.SproogyApp;
import com.sproogy.annotations.SproogyApplication;
import com.sproogy.app.ApplicationContext;
@SproogyApplication(basePackage = "com.example")
public class Main {
public static void main(String[] args) {
ApplicationContext context = SproogyApp.run(Main.class, args);
// Get the runner bean and execute
AppRunner runner = context.getBean(AppRunner.class);
runner.run();
}
}
Step 9: Run the Application
./gradlew run
Expected Output:
==================================================
My First Sproogy App v1.0.0
Max Login Attempts: 3
==================================================
Creating users...
[NOTIFICATION] Welcome email sent to: alice@example.com
✓ Created: User{id=1, username='alice', email='alice@example.com', active=true}
[NOTIFICATION] Welcome email sent to: bob@example.com
✓ Created: User{id=2, username='bob', email='bob@example.com', active=true}
[NOTIFICATION] Welcome email sent to: charlie@example.com
✓ Created: User{id=3, username='charlie', email='charlie@example.com', active=true}
All users:
- User{id=1, username='alice', email='alice@example.com', active=true}
- User{id=2, username='bob', email='bob@example.com', active=true}
- User{id=3, username='charlie', email='charlie@example.com', active=true}
Total users: 3
Deactivating Bob...
[NOTIFICATION] Account deactivated for: bob@example.com
✓ Bob deactivated: User{id=2, username='bob', email='bob@example.com', active=false}
Testing validation...
✓ Validation worked: Username already exists: alice
What Just Happened?
Let's break down what Sproogy did:
1. Component Scanning
Sproogy scanned com.example and found:
UserRepository(@Component)UserService(@Service)AppRunner(@Component)
2. Dependency Graph
Sproogy built this dependency graph:
AppRunner → UserService → UserRepository
3. Configuration Injection
Sproogy injected:
- From
.env:APP_NAME,APP_VERSION,MAX_USERS - From
application.yml:enableNotifications,maxLoginAttempts
4. Bean Creation Order
Sproogy created beans in dependency order:
UserRepository(no dependencies)UserService(depends onUserRepository)AppRunner(depends onUserService)
5. Field Injection
After creating each bean, Sproogy injected:
- Constructor parameters (e.g.,
UserRepositoryintoUserService) @Autowiredfields@Envfields (from environment variables)@Valuefields (from application.yml)
Experiment: Configuration Changes
Disable Notifications
Edit application.yml:
application:
features:
enableNotifications: false # Changed from true
Run again:
./gradlew run
Notice: No [NOTIFICATION] messages appear!
Change User Limit
Edit .env:
MAX_USERS=2 # Changed from 100
Run again. The third user creation will fail:
Exception: Maximum user limit reached: 2
Key Takeaways
🎯 Dependency Injection: Sproogy automatically wired UserRepository into UserService
🎯 Configuration: Values from .env and application.yml were injected into fields
🎯 Lifecycle: Sproogy managed the creation order based on dependencies
🎯 Testability: Each component can be tested in isolation (just pass mocks to constructors)
Testing Your Components
Here's how easy it is to unit test with dependency injection:
src/test/java/com/example/service/UserServiceTest.java:
package com.example.service;
import com.example.model.User;
import com.example.repository.UserRepository;
import org.junit.jupiter.api.Test;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
class UserServiceTest {
@Test
void testCreateUser() {
// Create mock repository
UserRepository mockRepo = mock(UserRepository.class);
when(mockRepo.count()).thenReturn(0L);
when(mockRepo.findByUsername("alice")).thenReturn(Optional.empty());
when(mockRepo.save(any(User.class))).thenAnswer(i -> {
User user = i.getArgument(0);
user.setId(1L);
return user;
});
// Inject mock into service (NO Sproogy needed for testing!)
UserService service = new UserService(mockRepo);
// Test
User user = service.createUser("alice", "alice@example.com");
assertEquals(1L, user.getId());
assertEquals("alice", user.getUsername());
assertTrue(user.isActive());
verify(mockRepo).save(any(User.class));
}
}
Notice: No Sproogy framework in the test! That's the power of dependency injection.
Next Steps
Congratulations! You've built your first Sproogy application with dependency injection and configuration management.
Now let's add socket communication:
Or dive deeper into dependency injection: