Thank you for your interest in contributing to SoftClient4ES! This document provides guidelines and best practices for contributing to the project.
- Getting Started
- Development Environment Setup
- Project Structure
- Development Workflow
- Coding Standards
- Testing Guidelines
- Pull Request Process
- Documentation
- Release Process
- Community
There are many ways to contribute to SoftClient4ES:
- 🐛 Report Bugs: Submit detailed bug reports with reproduction steps
- 💡 Suggest Features: Propose new features or improvements
- 📖 Improve Documentation: Fix typos, clarify explanations, add examples
- 🔧 Fix Issues: Pick up existing issues and submit pull requests
- ✨ Add Features: Implement new functionality
- 🧪 Write Tests: Improve test coverage
- 🔍 Review Pull Requests: Help review and test other contributors' work
Good first issues for newcomers are labeled with:
good-first-issue: Suitable for first-time contributorshelp-wanted: Issues where we need community helpdocumentation: Documentation improvements neededbug: Bug fixes neededenhancement: Feature enhancements
Browse open issues: GitHub Issues
- Check existing issues: Make sure your bug/feature hasn't been reported/requested
- Discuss major changes: For significant changes, open an issue first to discuss
- Read this guide: Understand our development workflow and standards
- Set up your environment: Follow the setup instructions below
- JDK: Java 17 or higher
- Scala: 2.12.x and 2.13.x
- sbt: 1.8.x or higher
- Git: Latest version
- Docker: For running Elasticsearch in tests
- IDE: IntelliJ IDEA (recommended) or VS Code with Metals
# Fork the repository on GitHub, then clone your fork
git clone https://github.com/YOUR_USERNAME/softclient4es.git
cd softclient4es
# Add upstream remote
git remote add upstream https://github.com/SOFTNETWORK-APP/SoftClient4ES.git
# Verify remotes
git remote -v# Compile the project
sbt compile
# Run tests (requires Docker)
sbt test
# Generate coverage report
sbt clean coverage test coverageReport
# Check code formatting
sbt scalafmtCheck
# Format code
sbt scalafmtAll- Install Scala plugin:
File > Settings > Plugins > Scala - Import project:
File > Openand selectbuild.sbt - Wait for sbt to download dependencies
- Enable scalafmt:
File > Settings > Editor > Code Style > Scala > Scalafmt
- Install Metals extension
- Open project folder
- Wait for Metals to import build
- Configure scalafmt in settings
# Run a simple test
sbt "testOnly *ElasticClientSpec"
# If successful, you're ready to contribute!softclient4es/
├── bridge/ # SQL to Elasticsearch DSL translation module
│ ├── src/
│ │ ├── main/scala/
│ │ │ └── app/softnetwork/elastic/sql/bridge
│ │ └── test/scala/
│ │ └── app/softnetwork/elastic/
│ │ └── sql/ # Unit tests
│ └── build.sbt
│
├── core/ # Core abstractions and interfaces
│ ├── src/
│ │ ├── main/scala/
│ │ │ └── app/softnetwork/elastic/
│ │ │ └── client/ # Client API interfaces
│ │ └── test/scala/
│ │ └── app/softnetwork/elastic/
│ │ └── client/ # Unit tests
│ └── build.sbt
│
├── documentation/ # Project documentation
│ ├── client/
│ ├── sql/
│ └── best-practices.md
│
├── es6/jest/ # Jest Client (Elasticsearch 6.x)
│ ├── src/
│ │ ├── main/scala/
│ │ │ └── app/softnetwork/elastic/client/
│ │ │ ├── jest/ # Jest implementation
│ │ │ └── spi/ # Jest SPI implementation
│ │ └── test/scala/
│ │ └── app/softnetwork/elastic/
│ │ ├── client/ # Client tests
│ │ └── persistence/ # Persistence tests
│ └── build.sbt
│
├── es6,es7/rest/ # Rest High Level client (Elasticsearch 6.x/7.x)
│ ├── src/
│ │ ├── main/scala/
│ │ │ └── app/softnetwork/elastic/client/
│ │ │ ├── rest/ # Rest High Level client implementation
│ │ │ └── spi/ # Rest High Level client SPI implementation
│ │ └── test/scala/
│ │ └── app/softnetwork/elastic/
│ │ ├── client/ # Rest High Level Client tests
│ │ └── persistence/ # Rest High Level Client Persistence tests
│ └── build.sbt
│
├── es8,es9/java/ # Java Client (Elasticsearch 8.x/9.x)
│ ├── src/
│ │ ├── main/scala/
│ │ │ └── app/softnetwork/elastic/client/
│ │ │ ├── jest/ # Java Client implementation
│ │ │ └── spi/ # Java Client SPI implementation
│ │ └── test/scala/
│ │ └── app/softnetwork/elastic/
│ │ ├── client/ # Java Client tests
│ │ └── persistence/ # Java Client Persistence tests
│ └── build.sbt
│
├── macros/ # SQL validation module
│ └── src/
│ └── main/scala/
│ └── app/softnetwork/elastic/sq/macros
│
├── macros-tests/ # SQL validation tests
│ └── test/
│ └── main/scala/
│ └── app/softnetwork/elastic/sq/macros
│
├── persistence/ # Akka persistence integration module
│ ├── src/
│ │ ├── main/scala/
│ │ │ └── app/softnetwork/elastic/persistence/
│ │ │ ├── query/ # Event Processor Stream
│ │ │ └── typed/
│ │ └── test/scala/
│ │ └── app/softnetwork/elastic/
│ │ └── client/ # Unit tests
│ └── build.sbt
│
├── project/ # sbt build configuration
│ ├── build.properties
│ ├── plugins.sbt
│ ├── SoftClient4es.scala
│ └── Versions.scala
│
├── sql/ # SQL translation module
│ ├── src/
│ │ ├── main/scala/
│ │ │ └── app/softnetwork/elastic/sql/
│ │ └── test/scala/
│ │ └── app/softnetwork/elastic/sql/
│ └── build.sbt
│
├── testkit/ # Testing utilities (Docker integration, test specs)
│ ├── src/
│ │ └── main/scala/
│ │ └── app/softnetwork/elastic/
│ │ ├── client/ # Client specifications for tests
│ │ ├── model/ # Entities for persistence tests
│ │ ├── persistence/ # Persistence specifications for tests
│ │ └── scalatest/ # Testkit utilities
│ └── build.sbt
│
├── .scalafmt.conf # Scalafmt configuration
├── .gitignore
├── build.sbt # Root build configuration
├── README.md
├── CONTRIBUTING.md
├── LICENSE
└── CHANGELOG.md| Module | Purpose | Dependencies |
|---|---|---|
sql |
SQL Parser | Gson |
bridge |
SQL to Elasticsearch DSL translation | sql, Elastic4s |
macros |
SQL Query type-safe validation | sql |
core |
Core abstractions, interfaces, and models | macros, Akka Streams, config |
persistence |
Akka persistence integration | core, Akka persistence |
jest |
Elasticsearch 6.x client implementation | core, Jest client |
rest |
Elasticsearch 7.x client implementation | core, REST High-Level client |
java |
Elasticsearch 8.x/9.x client implementation | core, Java API client |
testkit |
Testing utilities and Docker integration | core, persistence, Testcontainers |
Use descriptive branch names following this pattern:
<type>/<short-description>
Types:
- feature/ : New features
- fix/ : Bug fixes
- docs/ : Documentation changes
- refactor/ : Code refactoring
- test/ : Test improvements
- chore/ : Maintenance tasks
Examples:
feature/add-point-in-time-api
fix/bulk-operation-memory-leak
docs/update-migration-guide
refactor/simplify-error-handling
test/improve-scroll-api-coverage
chore/upgrade-elasticsearch-dependencies
# Update your fork
git checkout main
git fetch upstream
git merge upstream/main
# Create a new branch
git checkout -b feature/my-new-feature
# Push branch to your fork
git push -u origin feature/my-new-feature# Make your changes
# ...
# Check code formatting
sbt scalafmtCheck
# Format code if needed
sbt scalafmtAll
# Run tests
sbt test
# Run integration tests
sbt it:test
# Stage changes
git add .
# Commit with descriptive message
git commit -m "feat: add Point-in-Time API support for ES 8.x
- Implement openPointInTime and closePointInTime methods
- Add PIT support to search API
- Add comprehensive tests
- Update documentation
Closes #123"Follow the Conventional Commits specification:
<type>(<scope>): <subject>
<body>
<footer>
Types:
feat: New featurefix: Bug fixdocs: Documentation changesstyle: Code style changes (formatting, etc.)refactor: Code refactoringtest: Adding or updating testschore: Maintenance tasksperf: Performance improvements
Examples:
# Feature
git commit -m "feat(java): add Point-in-Time API support
Implement PIT API for Elasticsearch 8.x and 9.x with automatic
resource cleanup and comprehensive error handling.
Closes #123"
# Bug fix
git commit -m "fix(bulk): resolve memory leak in bulk operations
Fix memory leak caused by unclosed scroll contexts in bulk
streaming operations.
Fixes #456"
# Documentation
git commit -m "docs(best-practices): add section on connection pooling
Add detailed guidance on configuring connection pools for
different workload patterns."
# Breaking change
git commit -m "feat(core): change ElasticResult API to use Either
BREAKING CHANGE: ElasticResult now uses Either[ElasticError, T]
instead of custom success/failure types. Update pattern matching
accordingly.
Migration guide: documentation/migration.md
Closes #789"# Fetch latest changes from upstream
git fetch upstream
# Rebase your branch on upstream/main
git rebase upstream/main
# If conflicts occur, resolve them and continue
git add .
git rebase --continue
# Force push to your fork (only if you've already pushed)
git push --force-with-lease origin feature/my-new-feature
We follow the Scala Style Guide with some project-specific conventions.
✅ DO: Use ScalafmtAll for automatic formatting
# Format all files
sbt scalafmtAll
# Check formatting
sbt scalafmtCheckOur .scalafmt.conf:
version=3.0.2
style = defaultWithAlign
align.openParenCallSite = false
align.openParenDefnSite = false
align.tokens = [{code = "->"}, {code = "<-"}, {code = "=>", owner = "Case"}]
continuationIndent.callSite = 2
continuationIndent.defnSite = 2
danglingParentheses = true
indentOperator = spray
maxColumn = 100
indentOperator.preset = "spray"
danglingParentheses.preset = true
project.excludeFilters = [".*\\.sbt"]
rewrite.rules = [RedundantParens, SortImports]
spaces.inImportCurlyBraces = false
unindentTopLevelOperators = true
project.git = true// Classes and traits: PascalCase
class ElasticClientApi
trait SearchApi
// Objects: PascalCase
object ElasticClientFactory
// Methods and values: camelCase
def createIndex(name: String): ElasticResult[Boolean]
val maxConnections: Int = 100
// Constants: camelCase (not SCREAMING_SNAKE_CASE)
val defaultTimeout: FiniteDuration = 30.seconds
// Type parameters: Single uppercase letter or PascalCase
def search[T](query: String): ElasticResult[T]
def map[Result](f: T => Result): ElasticResult[Result]
// Package names: lowercase
package app.softnetwork.elastic.client✅ DO: Use meaningful variable names
// Good
val indexName: String = "users"
val searchResults: ElasticResult[SearchResponse] = client.search(indexName, query)
// Bad
val x: String = "users"
val r: ElasticResult[SearchResponse] = client.search(x, q)✅ DO: Add type annotations for public APIs
// Good: Public API with explicit return type
def search(index: String, query: String): ElasticResult[SearchResponse] = {
// Implementation
}
// Good: Private method can infer type
private def buildQuery(term: String) = {
s"""{"query":{"match":{"name":"$term"}}}"""
}
// Good: Complex types should be annotated
val config: ElasticConfig = ElasticConfig(
credentials = ElasticCredentials("http://localhost:9200")
)✅ DO: Use exhaustive pattern matching
// Good: Exhaustive matching
result match {
case ElasticSuccess(response) =>
logger.info(s"Success: ${response.id}")
case ElasticFailure(error) =>
logger.error(s"Failure: ${error.message}")
}
// Good: Use @switch annotation for performance
(status: @switch) match {
case 200 => "OK"
case 404 => "Not Found"
case 500 => "Server Error"
case _ => "Unknown"
}✅ DO: Add Scaladoc for public APIs
/**
* Searches for documents in the specified index.
*
* @param index the index name to search
* @param query the Elasticsearch query DSL as JSON string
* @param from the starting offset for pagination (default: 0)
* @param size the number of results to return (default: 10)
* @return ElasticSuccess with SearchResponse or ElasticFailure with error details
*
* @example
* {{{
* val query = """{"query":{"match":{"name":"John"}}}"""
* client.search("users", query, from = 0, size = 20) match {
* case ElasticSuccess(response) =>
* response.hits.foreach(hit => println(hit.source))
* case ElasticFailure(error) =>
* logger.error(s"Search failed: ${error.message}")
* }
* }}}
*/
def search(
index: String,
query: String,
from: Int = 0,
size: Int = 10
): ElasticResult[SearchResponse]✅ DO: Prefer immutable data structures
// Good: Immutable case class
case class ElasticConfig(
credentials: ElasticCredentials = ElasticCredentials(),
multithreaded: Boolean = true,
discovery: DiscoveryConfig,
connectionTimeout: Duration,
socketTimeout: Duration,
metrics: MetricsConfig
)
// Good: Immutable collections
val indices: Seq[String] = Seq("users", "products", "orders")
// Bad: Mutable collections
val indices: mutable.Buffer[String] = mutable.Buffer("users")✅ DO: Use functional composition
// Good: Functional composition
def validateAndIndex(index: String, document: String): ElasticResult[IndexResponse] = {
for {
validatedIndex <- validateIndexName(index)
validatedDoc <- validateJson(document)
response <- client.index(validatedIndex, validatedDoc)
} yield response
}
// Good: Map over results
client.search("users", query)
.map(_.hits)
.map(_.map(_.source))
.map(_.map(parseUser))We use ScalaTest with the AnyFlatSpecLike style.
import org.scalatest.flatspec.AnyFlatSpecLike
import org.scalatest.matchers.should.Matchers
class ElasticClientSpec extends AnyFlatSpecLike with Matchers {
"ElasticClient" should {
"index documents successfully" in {
val client = createTestClient()
val document = """{"name":"Alice","age":30}"""
val result = client.index("users", "user-0", document)
result.isSuccess shouldBe true
}
"handle indexing errors gracefully" in {
val client = createTestClient()
val invalidDocument = """invalid json"""
val result = client.index("users", invalidDocument)
result.isFailure shouldBe true
result.failed.get.message should include("JSON")
}
}
}✅ DO: Write unit tests for business logic
class SearchQueryBuilderSpec extends AnyWordSpec with Matchers {
"SearchQueryBuilder" should {
"build match query correctly" in {
val query = SearchQueryBuilder.matchQuery("name", "Alice")
query should include(""""match"""")
query should include(""""name"""")
query should include(""""Alice"""")
}
"build range query correctly" in {
val query = SearchQueryBuilder.rangeQuery("age", gte = Some(18), lte = Some(65))
query should include(""""range"""")
query should include(""""gte":18""")
query should include(""""lte":65""")
}
}
}✅ DO: Use ElasticDockerTestKit for integration tests
import app.softnetwork.elastic.scalatest.ElasticDockerTestKit
class ElasticClientIntegrationSpec
extends AnyWordSpec
with ElasticDockerTestKit
with Matchers {
"ElasticClient integration" should {
"perform full CRUD operations" in {
// Create index
val mapping = """{
"properties": {
"name": {"type": "text"},
"email": {"type": "keyword"}
}
}"""
client.createIndex("users", mapping = Some(mapping))
// Index document
val docId = "user-0"
val document = """{"name":"Alice","email":"alice@example.com"}"""
val indexResult = client.index("users", docId, document)
indexResult.isSuccess shouldBe true
// Refresh
client.refresh("users")
// Get document
val getResult = client.get("users", docId)
getResult.isSuccess shouldBe true
getResult.get.source should include("Alice")
// Update document
val update = """{"doc":{"name":"Alice Smith"}}"""
client.update("users", docId, update)
// Delete document
val deleteResult = client.delete("users", docId)
deleteResult.isSuccess shouldBe true
// Cleanup
client.deleteIndex("users")
}
}
}✅ DO: Maintain high test coverage
# Generate coverage report
sbt clean coverage test coverageReport
# View report
open target/scala-2.13/scoverage-report/index.htmlCoverage Goals:
- Core API: > 90%
- Client implementations: > 85%
- Utility functions: > 80%
- Overall project: > 85%
✅ DO: Use descriptive test names
// Good: Clear what is being tested
"index documents with auto-generated IDs" in { ... }
"handle connection timeout errors" in { ... }
"validate index names before creation" in { ... }
// Bad: Unclear test purpose
"test1" in { ... }
"it works" in { ... }✅ DO: Use realistic test data
object TestData {
val validUser: String = """{
"name": "Alice Smith",
"email": "alice@example.com",
"age": 30,
"created_at": "2024-01-15T10:30:00Z"
}"""
val validProduct: String = """{
"name": "Laptop",
"price": 999.99,
"category": "electronics",
"in_stock": true
}"""
val invalidJson: String = """{"name": "Alice", "age": }"""
}✅ DO: Use ScalaTest's async support
import org.scalatest.concurrent.ScalaFutures
import scala.concurrent.duration._
class AsyncClientSpec
extends AnyWordSpec
with Matchers
with ScalaFutures {
implicit val patience: PatienceConfig = PatienceConfig(
timeout = 10.seconds,
interval = 100.millis
)
"AsyncClient" should {
"index documents asynchronously" in {
val future = asyncClient.indexAsync("users", document)
whenReady(future) { result =>
result.isSuccess shouldBe true
result.get.id should not be empty
}
}
}
}✅ Checklist:
- Code compiles without errors
- All tests pass (
sbt test) - Code is formatted (
sbt scalafmtAll) - No scalafmt violations (
sbt scalafmtCheck) - Test coverage is maintained or improved
- Documentation is updated
- CHANGELOG.md is updated
- Commit messages follow conventions
- Branch is rebased on latest
main
- Push your branch to your fork
git push origin feature/my-new-feature
- Create Pull Request on GitHub
- Go to your fork on GitHub
- Click "New Pull Request"
- Select your feature branch
- Fill out the PR template
## Description
Brief description of the changes in this PR.
## Type of Change
- [ ] Bug fix (non-breaking change fixing an issue)
- [ ] New feature (non-breaking change adding functionality)
- [ ] Breaking change (fix or feature causing existing functionality to change)
- [ ] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring
## Related Issues
Closes #123
Fixes #456
## Changes Made
- Added Point-in-Time API support for ES 8.x
- Implemented automatic resource cleanup
- Added comprehensive tests
- Updated documentation
## Testing
Describe the tests you ran and how to reproduce them.
- [ ] Unit tests added/updated
- [ ] Integration tests added/updated
- [ ] Manual testing performed
### Test Environment
- Elasticsearch version: 8.11.0
- Scala version: 2.13.12
- JDK version: 11
## Documentation
- [ ] README updated
- [ ] API documentation updated
- [ ] Migration guide updated (if breaking change)
- [ ] CHANGELOG updated
## Screenshots (if applicable)
Add screenshots for UI changes or visual documentation.
## Checklist
- [ ] Code compiles without errors
- [ ] All tests pass
- [ ] Code is formatted with scalafmt
- [ ] Test coverage maintained/improved
- [ ] Documentation updated
- [ ] Commit messages follow conventions
- [ ] Branch rebased on latest main
## Additional Notes
Any additional information reviewers should know.
- Automated Checks: CI/CD pipeline runs automatically
- Compilation
- Unit tests
- Code formatting
- Coverage report
- Code Review: Maintainers review your code
- Code quality
- Test coverage
- Documentation
- Design decisions
- Feedback: Address review comments
- Make requested changes
- Push updates to your branch
- Respond to comments
- Approval: Once approved, maintainers will merge
# Make requested changes
# ...
# Commit changes
git add .
git commit -m "refactor: address review comments
- Simplify error handling logic
- Add missing test cases
- Update documentation"
# Push to your branch
git push origin feature/my-new-feature# Update your local main branch
git checkout main
git fetch upstream
git merge upstream/main
# Delete your feature branch
git branch -d feature/my-new-feature
git push origin --delete feature/my-new-feature- Code Documentation: Scaladoc comments in code
- API Documentation: Detailed API reference
- User Guides: How-to guides and tutorials
- Best Practices: Recommended patterns
- Migration Guides: Version upgrade instructions
- API Reference:
documentation/client/ - User Guides:
documentation/guides/ - Best Practices:
documentation/best-practices.md - Migration Guides:
documentation/migration.md - Examples:
examples/
We follow Semantic Versioning:
MAJOR.MINOR.PATCH
- MAJOR: Breaking changes
- MINOR: New features (backward compatible)
- PATCH: Bug fixes (backward compatible)
- Create Release Branch
git checkout -b release/v0.12.0- Update Version
// build.sbt
version := "0.12.0"- Update CHANGELOG
## [0.12.0] - 2025-11-08
### Added
- Point-in-Time API support for ES 8.x
- Automatic resource cleanup
### Changed
- Improved error messages
### Fixed
- Memory leak in bulk operations
### Breaking Changes
- None-
Create Release PR
-
After Merge, Tag Release
git tag -a v0.12.0 -m "Release version 0.12.0"
git push upstream v0.12.0- Publish Artifacts
sbt +publish
- 💬 GitHub Discussions: Discussions
- 🐛 GitHub Issues : Issues
- 📧 Email: admin@softnetwork.fr
- Search existing issues: Your question might already be answered
- Check documentation: Review the docs before asking
- Ask in Discussions: For general questions and discussions
- Open an issue: For bugs or feature requests
- Answer questions in Discussions
- Review pull requests
- Improve documentation
- Share your use cases and examples
We value all contributions! Contributors are recognized in:
- CONTRIBUTORS.md file
- Release notes
- Project README
# Setup
git clone https://github.com/YOUR_USERNAME/softclient4es.git
cd softclient4es
sbt compile
# Development
sbt test # Run tests
sbt scalafmtAll # Format code
sbt scalafmtCheck # Check formatting
# Coverage
sbt clean coverage test coverageReport
# Create branch
git checkout -b feature/my-feature
# Commit
git add .
git commit -m "feat: add new feature"
# Push
git push origin feature/my-feature
# Update branch
git fetch upstream
git rebase upstream/main
git push --force-with-lease origin feature/my-feature- 📖 Scala Style Guide
- 📖 Conventional Commits
- 📖 Semantic Versioning
- 📖 ScalaTest Documentation
- 📖 sbt Documentation
Thank you for contributing to SoftClient4ES! Your contributions help make this project better for everyone.
Questions? Feel free to ask in GitHub Discussions or email us at admin@softnetwork.fr.
Last Updated: 2025-11-12
Version: 0.12.0
Built with ❤️ by the SoftNetwork community