{"id":19808405,"url":"https://github.com/limitium/sofa","last_synced_at":"2026-05-15T19:31:17.652Z","repository":{"id":252426730,"uuid":"840325695","full_name":"limitium/sofa","owner":"limitium","description":null,"archived":false,"fork":false,"pushed_at":"2026-03-31T15:44:40.000Z","size":3764,"stargazers_count":0,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-03-31T16:29:01.570Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/limitium.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-08-09T13:02:38.000Z","updated_at":"2026-03-31T15:44:49.000Z","dependencies_parsed_at":"2024-11-07T19:20:29.560Z","dependency_job_id":"72a3a706-d544-4763-ad27-ce512ea03bca","html_url":"https://github.com/limitium/sofa","commit_stats":null,"previous_names":["limitium/sofa"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/limitium/sofa","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/limitium%2Fsofa","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/limitium%2Fsofa/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/limitium%2Fsofa/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/limitium%2Fsofa/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/limitium","download_url":"https://codeload.github.com/limitium/sofa/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/limitium%2Fsofa/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33076153,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-15T11:35:32.926Z","status":"ssl_error","status_checked_at":"2026-05-15T11:35:31.362Z","response_time":103,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-12T09:13:47.802Z","updated_at":"2026-05-15T19:31:17.644Z","avatar_url":"https://github.com/limitium.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# SOFA - Schema-Oriented Framework for Avro\n\nSOFA is a flexible code generation framework that transforms Avro schemas into various target formats using customizable templates. It provides a powerful way to generate code, documentation, or any text-based output while maintaining complex relationships between Avro records.\n\n## Features\n\n- **Template-Based Generation**: Uses Pebble templating engine for flexible code generation\n- **Multiple Output Formats**: Can generate multiple outputs from the same schema\n- **Relationship Awareness**: Understands and preserves record relationships and dependencies\n- **Type System Support**: Built-in type converters for various target platforms:\n    - Java\n    - Flatbuffers\n    - LiquidBase\n    - Apache Connect\n- **Customizable Naming**: Configurable naming strategies for namespaces, classes, and files\n- **Filtering**: Supports white/black listing of entities for selective generation\n- **Post-Generation Hooks**: Ability to run commands after generation\n\n## Installation\n\nAdd the following dependency to your project:\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eart.limitium.sofa\u003c/groupId\u003e\n    \u003cartifactId\u003esofa\u003c/artifactId\u003e\n    \u003cversion\u003e1.0.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n## Usage\n\n1. Create a YAML configuration file defining your generation rules:\n\n```yaml\nschemas:\n  - path/to/schema1.avsc\n  - path/to/schema2.avsc\n\nvalues:\n  packageName: \"com.example\"\n  version: \"1.0.0\"\n\ngenerators:\n  - path: \"generators/java\"\n    templates:\n      namespace: \"{{packageName}}\"\n      name: \"{{schema.name}}\"\n      fullname: \"{{namespace}}.{{name}}\"\n      folder: \"src/main/java/{{namespace | replace('.', '/')}}\"\n      filename: \"{{name}}.java\"\n    filters:\n      white:\n        - \"com.example.User\"\n        - \"com.example.Order\"\n```\n\n2. Run the generator:\n\n```bash\njava -jar sofa.jar path/to/config.yaml\n```\n\n## Template Types in Detail\n\nSOFA uses different templates to handle various entity relationships and types. Each template serves a specific purpose in the code generation process:\n\n### Root Template (`root.peb`)\nUsed for generating root record entities that have no parent dependencies. Root records typically represent:\n- Top-level domain objects\n- Aggregate roots in DDD terms\n- Entry points for object graphs\n\nExample use case: Generating main entity classes that own other entities.\n\n```java\n// Example root template usage\npublic class {{name}} {\n    private final String id;\n    {% for owned in entity.dependencies %}\n    private final List\u003c{{owned.name}}\u003e {{owned.name | toSnakeCase}}s;\n    {% endfor %}\n}\n```\n\n### Messages vs Entities\n\nThe framework distinguishes between two key concepts:\n\n- **Messages**: Self-contained documents that include all related data inline. Messages are designed for data transfer and typically denormalized, making them ideal for event-driven systems and API payloads. When a message includes related data, it embeds the complete related object directly in the message structure.\n\nIn this model:\n* One-to-one relationships are embedded directly in the parent record\n* One-to-many relationships are represented as arrays within the parent record\n* All related data is included in a single document\n* Ideal for event-driven systems and message passing architectures\n\n- **Entities**: Database-oriented structures that follow relational database normalization principles. Entities use references (typically through primary keys) to establish relationships between objects, rather than embedding the complete related data. This approach is optimized for data storage and maintains referential integrity through foreign key relationships.\n\nIn this model:\n* One-to-one relationships are embedded in the parent entity\n* One-to-many relationships are extracted into separate entities with an owner_id reference\n* Relationships are maintained through foreign key references\n* Ideal for relational databases and systems requiring normalized data\n\nFor example, consider an Order with LineItems:\n\na message:\n```json\n{\n  \"orderId\": \"123\",\n  \"items\": [\n    { \"productId\": \"A1\", \"quantity\": 2 },\n    { \"productId\": \"B2\", \"quantity\": 1 }\n  ]\n}\n```\n\nthe same as the entities\n```json\nOrder {\n  id: \"123\"\n}\n\nOrderItem {\n  id: \"item1\",\n  order_id: \"123\",\n  product_id: \"A1\",\n  quantity: 2\n}\nOrderItem {\n  id: \"item2\",\n  order_id: \"123\",\n  product_id: \"B2\",\n  quantity: 1\n}\n```\n\nThe schema generator can handle both approaches, choosing the appropriate template based on whether the record is marked as an owner (containing arrays) or dependent (owned by another record).\n\n### Child Template (`child.peb`)\nUsed for records that are neither root nor involved in one-to-many relationships. Child records are typically:\n- Value objects\n- Component parts of larger entities\n- Supporting data structures\n\nExample use case: Generating embedded/component classes.\n\n```java\n// Example child template usage\npublic class {{name}} {\n    {% for field in entity.fields %}\n    private {{field.type | javaType}} {{field.name}};\n    {% endfor %}\n}\n```\n\n### Owner Template (`owner.peb`)\nUsed for records that contain one-to-many relationships with other records. Owner records:\n- Manage collections of other entities\n- Control lifecycle of dependent entities\n- Implement parent-side of relationships\n\nExample use case: Generating container classes with collection management.\n\n```java\n// Example owner template usage\npublic class {{name}} {\n    {% for field in entity.fields | recordLists %}\n    private List\u003c{{field.type.elementType | javaType}}\u003e {{field.name}};\n\n    public void add{{field.name | capitalize}}({{field.type.elementType | javaType}} item) {\n        {{field.name}}.add(item);\n    }\n    {% endfor %}\n}\n```\n\n### Dependent Template (`dependent.peb`)\nUsed for records that are owned by other records in one-to-many relationships. Dependent records:\n- Belong to parent entities\n- Have their lifecycle managed by owners\n- Implement child-side of relationships\n\nExample use case: Generating entities that are always part of a collection.\n\n```java\n// Example dependent template usage\npublic class {{name}} {\n    private final {{entity.owners[0].name}} owner;\n\n    public {{name}}({{entity.owners[0].name}} owner) {\n        this.owner = owner;\n    }\n\n    {% for field in entity.fields %}\n    private {{field.type | javaType}} {{field.name}};\n    {% endfor %}\n}\n```\n### Record Template (`record.peb`)\nUsed as a fallback template for any record type that doesn't match more specific templates. This template is:\n- The most generic template type\n- Used when no other template matches\n- Suitable for basic record generation regardless of relationships\n\nRecord templates typically handle:\n- Basic field generation\n- Common methods (getters/setters)\n- Standard class structure\n\nExample use case: Generating standard data classes or when relationship-specific templates are not needed.\n\n```java\n// Example record template usage\npublic class {{name}} {\n  {% for field in entity.fields %}\n  private {{field.type | javaType}} {{field.name}};\n  {% endfor %}\n\n  public {{name}}() {}\n\n  {% for field in entity.fields %}\n  public {{field.type | javaType}} get{{field.name | capitalize}}() {\n      return {{field.name}};\n  }\n\n  public void set{{field.name | capitalize}}({{field.type | javaType}} {{field.name}}) {\n      this.{{field.name}} = {{field.name}};\n  }\n  {% endfor %}\n\n  {% if entity.fields | recordLists %}\n  // Collection management methods\n  {% for field in entity.fields | recordLists %}\n  public void add{{field.name | capitalize | singular}}({{field.type.elementType | javaType}} item) {\n      if (this.{{field.name}} == null) {\n            this.{{field.name}} = new ArrayList\u003c\u003e();\n      }\n      this.{{field.name}}.add(item);\n  }\n  {% endfor %}\n  {% endif %}\n}\n```\n### Enum Template (`enum.peb`)\nUsed for generating enum types. Supports:\n- Basic enum generation\n- Enum with additional properties\n- Enum with aliases/descriptions\n\nExample use case: Generating type-safe enumeration classes.\n\n```java\n// Example enum template usage\npublic enum {{name}} {\n    {% for symbol in symbols %}\n    {{symbol}}{% if not loop.last %},{% endif %}\n    {% endfor %}\n}\n```\n\n### Template Selection Priority\n\nWhen multiple templates are available, SOFA selects the most specific template in this order:\n1. `enum.peb` for enum types\n2. `root.peb` for root records\n3. `owner.peb` for records with collections\n4. `child.peb` for non-root records\n5. `dependent.peb` for records owned by others\n6. `record.peb` as final fallback for any record type\n\n## Template Functions\n\nSOFA provides various template filters to help with code generation:\n\n- Case conversion: `toSnakeCase`, `toCamelCase`\n- Type conversion: `javaType`, `fbType`, `liquidBaseType`\n- Dependency traversal: `dependenciesRecursiveAll`, `dependenciesRecursiveUpToClosestDependent`\n- Structure flattening: `flattenFields`, `flattenRecords`, `flattenOwners`\n- Entity filtering: `enums`, `recordLists`, `noRecordLists`\n\n## Plugin system\n\nPlugins are ordinary jars on the generator runtime classpath. They can contribute:\n\n- **Pebble filters** (merged into `CustomExtension#getFilters()`)\n- **Type converters** (appended to the converter list used by `Factory`)\n\n### How discovery works\n\nPlugins are loaded from `plugins` in def.yaml using fully-qualified class names:\n\n```yaml\nplugins:\n  - \"com.mycompany.sofa.MyPlugin\"\n  - \"com.other.Plugin\"\n```\n\nEach class must implement `art.limitium.sofa.plugin.SofaPlugin` (published as\n`sofa-plugin-api`) and have a public no-arg constructor.\n\n### How to add a plugin jar\n\n- **Drop-in jar**: put your plugin jar into `schema/libs/` (picked up automatically via Gradle `runtimeOnly fileTree(...)`).\n- **As a dependency**: add it to `schema/build.gradle`:\n\n```gradle\ndependencies {\n  runtimeOnly \"your.group:your-artifact:1.0.0\"\n}\n```\n\n### How to write a plugin\n\nYour plugin module should:\n\n- depend on `art.limitium.sofa:sofa-plugin-api:\u003cversion\u003e` (JDK-only API)\n- implement `art.limitium.sofa.plugin.SofaPlugin` and return:\n  - `SofaPlugin.SofaFilter` implementations (generator will proxy them into Pebble filters)\n  - `SofaPlugin.SofaTypeConverter\u003c?\u003e` implementations operating on `art.limitium.sofa.plugin.SofaType`\n    (generator will adapt internal schema `Type` into `SofaType` and proxy into SOFA `TypeConverter`)\n\n## Example\n\nGiven an Avro schema:\n\n```json\n{\n  \"type\": \"record\",\n  \"name\": \"User\",\n  \"namespace\": \"com.example\",\n  \"fields\": [\n    {\"name\": \"id\", \"type\": \"string\", \"logicalType\": \"uuid\"},\n    {\"name\": \"name\", \"type\": \"string\"},\n    {\"name\": \"status\", \"type\": \"enum\", \"name\": \"UserStatus\", \"symbols\": [\"ACTIVE\", \"INACTIVE\"]}\n  ]\n}\n```\n\nAnd a Java template:\n\n```java\npackage {{namespace}};\n\npublic class {{name}} {\n    {% for field in entity.fields %}\n    private {{field.type | javaType}} {{field.name}};\n    {% endfor %}\n}\n```\n\nSOFA will generate:\n\n```java\npackage com.example;\n\npublic class User {\n    private String id;\n    private String name;\n    private UserStatus status;\n}\n```\n\n## Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request.\n\n## TODO\n\n- External conditions per template generation\n- Smart override detection for unchanged entities\n- Extension loading from classpath\n- Gradle plugin/script integration\n- Example tests\n- Comprehensive documentation\n\n## License\n\nThis project is licensed under the MIT License - see the LICENSE file for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flimitium%2Fsofa","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flimitium%2Fsofa","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flimitium%2Fsofa/lists"}