# nautchkafe-scylladb 
Lightweight, functional, modular, and flexible way to interact system built with ScyllaDB. scylla.db
To get started, clone the repository and build the project:
git clone https://github.com/noyzys/nautchkafe-scylladb.git
cd nautchkafe-scylladbNavigate to the project directory:
./gradlew build- ScyllaDB Integration: Provides seamless interaction with ScyllaDB via the Java Driver.
- Functional Programming with Vavr: Utilizes Vavr’s Try,Option, and other functional types to handle errors, nulls, and side effects more elegantly.
- Modular & Extensible: Components such as repositories, queries, and session management are decoupled and highly modular.
- Asynchronous and Synchronous Operations: Supports both async and sync operations, making it suitable for high-performance applications.
- SQL Constants: The SQL queries are abstracted and managed via constants for better maintainability and readability.
- findAll() - Retrieves all rows from a table.
- find() - Retrieves a row based on a custom predicate.
- save() - Saves an entity to the database.
- delete() - Deletes an entity based on a predicate.
- findAllAsync() - Retrieves all rows asynchronously.
- findAsync() - Retrieves a row asynchronously based on a predicate.
- saveAsync() - Saves an entity asynchronously.
- deleteAsync() - Deletes an entity asynchronously.
final class ScyllaApplication {
    public static void main(String[] args) {
        // Creating a session manager
        ScyllaSessionCoordinator sessionManager = new ScyllaSessionCoordinator();
        Try<ScyllaResource<Session>> session = sessionManager.openSession("localhost", "keyspace");
        session.onSuccess(resource -> {
            // Using the session
            resource.use(session -> {
                // Initializing query executor
                QueryExecutor executor = new ScyllaQueryExecutor(session);
                // Define user repository with functional bindings and predicate handling
                ScyllaResultMapper<ScyllaUser> userMapper = row -> new ScyllaUser(
                        row.getColumn("name", String.class),
                        row.getColumn("age", Integer.class)
                );
                // Define predicate sql operations of mappings
                Function<ScyllaUser, List<Object>> userBinder = user -> List.of(user.name(), user.age());
                ScyllaPredicateSQL<ScyllaUser> userPredicateSQL = new ScyllaPredicateSQL<ScyllaUser>(List.empty())
                        .addMapping(user -> Option.of("age > 25").filter(ignore -> user.age() > 25))
                        .addMapping(user -> Option.of("name = ?").filter(ignore -> user.name() != null));
                ScyllaDBRepository<ScyllaUser> userRepository = new ScyllaDBRepository<>(
                        executor, userMapper, userBinder, "users", userPredicateSQL
                );
                // Table setup
                ScyllaTableCoordinator tableCoordinator = new ScyllaTableCoordinator(executor);
                tableCoordinator.createTable("users", "name TEXT PRIMARY KEY, age INT")
                        .onFailure(err -> System.err.println("Failed to create table: " + err.getMessage()));
                // Save a user
                ScyllaUser newUser = new ScyllaUser("Frankie", 30);
                userRepository.save(newUser)
                        .onSuccess(res -> System.out.println("User saved"))
                        .onFailure(err -> System.err.println("Failed to save user: " + err.getMessage()));
                // Fetch all users with pagination
                userRepository.findAll(10, 0).onSuccess(users -> users.forEach(user -> System.out.println("Found user: " + user.name())))
                        .onFailure(err -> System.err.println("Failed to fetch users: " + err.getMessage()));
                // Fetch users asynchronously
                userRepository.findAllAsync().thenAccept(users ->
                        users.forEach(user -> System.out.println("Async user: " + user.name())))
                    .exceptionally(err -> {
                    System.err.println("Failed to fetch users asynchronously: " + err.getMessage());
                    return null;
                });
                // Fetch with a predicate
                ScyllaUser predicateUser = new ScyllaUser(null, 25);
                userRepository.findWithPredicate(predicateUser).onSuccess(users -> users.forEach(user -> System.out.println("User matching predicate: " + user.name())))
                        .onFailure(err -> System.err.println("Failed to fetch with predicate: " + err.getMessage()));
                // Delete with a predicate
                userRepository.deleteWithPredicate(newUser)
                        .onSuccess(res -> System.out.println("User deleted"))
                        .onFailure(err -> System.err.println("Failed to delete user: " + err.getMessage()));
                // Drop table
                tableCoordinator.dropTable("users")
                        .onFailure(err -> System.err.println("Failed to drop table: " + err.getMessage()));
                // Close session
                sessionManager.closeSession(session);
            });
        }).onFailure(e -> System.err.println("Failed to connect to ScyllaDB: " + e.getMessage()));
    }
}1. Session Coordinator: Establishing Connection
ScyllaSessionCoordinator sessionManager = new ScyllaSessionCoordinator();
Try<ScyllaResource<Session>> session = sessionManager.openSession("localhost", "keyspace");- Function: Establish a connection to the ScyllaDB using the ScyllaSessionCoordinator.
- Explanation: The openSession function tries to create a session with ScyllaDB, establishing a connection to the specified keyspace.
2. Using the Session
session.onSuccess(resource -> {
    resource.use(session -> {
        // The session is now ready to be used for queries and transactions.
    });
});- Function: The use method of ScyllaResource ensures the session is used for the scope of the block and safely closed after.
- Explanation: The session is passed to the repository or query executor inside the use block, and it will be closed automatically after the operations.
3. Initializing the Query Executor
QueryExecutor executor = new ScyllaQueryExecutor(session);4. Creating the Repository
ScyllaRepository<ScyllaUser> userRepository = new ScyllaDBRepository(
    executor, "users", ScyllaUser::id, ScyllaUser::toBindValues, ScyllaUser::mapRow
);- **Function: Create a ScyllaRepository for the ScyllaUser entity.
- The repository is responsible for CRUD operations, and is parameterized with the ScyllaUser entity. It takes functions such as User::id, User::toBindValues, and User::mapRow to handle primary key extraction, data binding, and row mapping.
5. Saving a User
ScyllaUser newUser = new ScyllaUser("Nautchkafe", 30);
userRepository.save(newUser).onSuccess(res -> System.out.println("User saved"));- Function: Save a new user to the database.
- Explanation: The save method binds the newUser to the insert statement using toBindValues and executes the query
6. Fetching All Users
userRepository.findAll().onSuccess(users -> users.forEach(user -> System.out.println("Found user: " + user.name())));7. Deleting a User
userRepository.delete(user -> user.name().equals("Nautchkafe")).onSuccess(res -> System.out.println("User deleted");8. Mapping Result Rows
ScyllaResultMapper<ScyllaUser> userMapper = row -> new ScyllaUser(
    row.getColumn("name", String.class),
    row.getColumn("age", Integer.class)
);9. Binding User Data to Query
Function<User, List<Object>> userBinder = user -> List.of(user.name(), user.age());10. Creating a Table
ScyllaTableCoordinator tableCoordinator = new ScyllaTableCoordinator(queryExecutor);
Try<Void> createTable = tableCoordinator.createTable("users", "name TEXT PRIMARY KEY, age INT");11. Inserting Data
ScyllaUser user = new ScyllaUser("Nautchkafe", 30);
Try<Void> saveResult = userRepository.save(user);12. Fetching Users Asynchronously
CompletableFuture<Option<ScyllaUser>> userOptionAsync = userRepository.findAsync(user -> user.age() > 25);
userOptionAsync.thenAccept(userOpt -> userOpt
    .peek(user -> System.out.println("Found user: " + user.name()))
    .onEmpty(() -> System.out.println("No user found"))
);13. Deleting Data Asynchronously
CompletableFuture<Void> deleteAsyncResult = userRepository.deleteAsync(user -> user.name().equals("Nautchkafe"));
deleteAsyncResult.thenRun(() -> System.out.println("User deleted"));14. Dropping a Table
Try<Void> dropTableTry = tableCoordinator.dropTable("users");
if (dropTableTry.isFailure()) {
    System.err.println("Failed to drop table");
}Closing the Session
sessionCoordinator.closeSession(session);- Handles the execution of SQL queries on ScyllaDB. It supports both synchronous and asynchronous query execution.
- Manages the session lifecycle and ensures connection to ScyllaDB. It is responsible for opening and closing sessions.
- A wrapper around Trythat ensures the safe use of resources, automatically closing them once done. Theusemethod ensures that resources are properly handled.
- Handles operations related to ScyllaDB tables such as creating, dropping, truncating, and checking if a table exists.
- A functional interface that maps a Rowfrom ScyllaDB to a custom object. This can be used for mapping result sets from queries to domain objects.
This example showcases how to integrate ScyllaDB with a functional approach.
If you are interested in exploring functional programming and its applications within this project visit the repository at vavr-in-action, fp-practice.