{"id":20181959,"url":"https://github.com/clean-arch-enablers-project/cae-framework","last_synced_at":"2025-04-10T05:10:57.710Z","repository":{"id":229573897,"uuid":"777056326","full_name":"clean-arch-enablers-project/cae-framework","owner":"clean-arch-enablers-project","description":"Repository for the open source CAE framework designed to make the experience of developing software with clean architecture easier.","archived":false,"fork":false,"pushed_at":"2024-10-29T23:31:05.000Z","size":299,"stargazers_count":9,"open_issues_count":1,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-10-30T01:52:20.023Z","etag":null,"topics":["clean-architecture","core-layer","framework","java"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/clean-arch-enablers-project.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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}},"created_at":"2024-03-25T05:25:35.000Z","updated_at":"2024-10-29T23:31:09.000Z","dependencies_parsed_at":null,"dependency_job_id":"024eec28-e951-4503-a4fa-161f2a932722","html_url":"https://github.com/clean-arch-enablers-project/cae-framework","commit_stats":null,"previous_names":["clean-arch-enablers-project/cae-framework"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clean-arch-enablers-project%2Fcae-framework","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clean-arch-enablers-project%2Fcae-framework/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clean-arch-enablers-project%2Fcae-framework/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clean-arch-enablers-project%2Fcae-framework/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/clean-arch-enablers-project","download_url":"https://codeload.github.com/clean-arch-enablers-project/cae-framework/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248161276,"owners_count":21057555,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["clean-architecture","core-layer","framework","java"],"created_at":"2024-11-14T02:37:16.359Z","updated_at":"2025-04-10T05:10:57.678Z","avatar_url":"https://github.com/clean-arch-enablers-project.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ✔️ cae-framework\n☕ Java \u0026 Kotlin edition\n\n\u003cbr\u003e\n\nWelcome to the open source cae-framework repository! This component of the SDK is designed to enable the [Angularization](https://github.com/clean-arch-enablers-project#-the-angularization-concept) effect of client applications by following Clean Architecture principles.\n\n### ▶️ The artifact:\n\n```xml\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.clean-arch-enablers\u003c/groupId\u003e\n  \u003cartifactId\u003ecae-framework\u003c/artifactId\u003e\n  \u003cversion\u003e${version}\u003c/version\u003e\n\u003c/dependency\u003e\n```\n###### All available versions can be found here: [cae-framework on Maven](https://central.sonatype.com/artifact/com.clean-arch-enablers/cae-framework) (_since it is in a snapshot state, it is recommended to always use the latest version._) \n\n\u003cbr\u003e\n\nState Symbol Key:\n\n- ``✅`` — _Under release state_\n- ``✔️`` — _Under snapshot state_\n- ``⏳`` — _Under full development state_\n\n\u003cbr\u003e\n\n## 📚 Key Concepts\n### ⚙️ Use Cases\nThe core of the CAE Framework revolves around use cases. Each use case is a distinct, self-contained unit of functionality, designed to be easily maintained, extended, and tested.\n\n##### 🎨 Types of Use Cases\n- ``✔️`` ``FunctionUseCase``: Receives input and returns output.\n- ``✔️`` ``ConsumerUseCase``: Receives input but does not return output.\n- ``✔️`` ``SupplierUseCase``: Returns output without any input.\n- ``✔️`` ``RunnableUseCase``: Neither receives input nor returns output.\n\nThat's how you declare Use Cases:\n\n```java\n//will receive objects of type GetBotAccountsUseCaseInput and return ones of type GetBotAccountUseCaseOutput\npublic abstract class GetBotAccountsUseCase extends FunctionUseCase\u003c\n        GetBotAccountsUseCaseInput,\n        GetBotAccountsUseCaseOutput\u003e {}\n```\n\n```java\n//will receive objects of type UpdateUserAccountProfileUseCaseInput and return nothing\npublic abstract class UpdateUserAccountProfileUseCase extends ConsumerUseCase\u003cUpdateUserAccountProfileUseCaseInput\u003e {}\n```\n\n```java\n//wont receive anything as input, but will return an object of type GetUserAccountProfilesUseCaseOutput\npublic abstract class GetUserAccountProfilesUseCase extends SupplierUseCase\u003cGetUserAccountProfilesUseCaseOutput\u003e {}\n```\n\n```java\n//neither receives or returns anything; it only executes something\npublic abstract class DeleteInactiveLeadsUseCase extends RunnableUseCase {}\n```\n\nUse Case types which accept input require the parameterized generic type of the input to be a subclass of ```UseCaseInput```, this way the Use Case can leverage the ```UseCaseInput``` API for input validation rules. For the output types nothing is required.\n\n\u003cbr\u003e\n\n#### ▶️ Use Case Execution\nEvery Use Case subtype will inherit the same API for getting executed:\n\n```UseCase::execute```\n\nThe difference between them all is only that some accept input and/or return output and others don't do either or at least one of the options. Regardless, everyone of them accepts the following parameter: an object of type ```ExecutionContext```. This object serves the purpose of identifying each request with a unique ID, so troubleshootings can rely on the execution context at the log level of analysis, for example. \n\nThe ```ExecutionContext``` object keeps an attribute called ```correlationId``` which is the UUID that identifies each execution. It can be generated randomly or provided programmatically:\n\n```java\n//generating random correlationId\nvar random = ExecutionContext.ofNew();\n\n//providing a previous set correlationId\nvar correlationId = UUID.randomUUID().toString();\nvar previouslyEstablished = ExecutionContext.of(correlationId)\n```\n\nThe random approach serves well when the workflow begins at that point, but in case the flow starts at the frontend app, for example, it is interesting for the frontend app to generate a correlationId in UUID and pass it down to the backend service where the Use Case is gonna be executed and programmatically pass it as the correlationId of the Execution Context the Use Case will consume. This way the step-by-step can be monitored even throughout different applications of the stack.\n\n\nWhen executed, a Use Case can have some side behaviors:\n\n- ``✔️`` Autolog\n- ``✔️`` Auto input validation\n- ``⏳`` Autocache\n- ``⏳`` Autonotify\n- ``✔️`` Scope based authorization validation\n- ``⏳`` Role based authorization validation\n\n##### 📄 Autolog\nWhenever an instance of use case gets executed, an automatic log will be generated. It can be in two modes:\n\n- structured\n- natural language\n\nThe structured mode is a JSON payload with the log data, while the natural language mode is basically a simple text. However, both can display the same data; they just differ in their presentation styles.\n\nThe contained info is:\n\n- The use case: the name of the use case which is being executed (the object's class name)\n- Execution correlation Id: an unique identifier per execution (can be parameterized or randomly generated)\n- Whether or not successful: if the flow didn't throw any exceptions\n- Exception thrown (in case of unsuccessful executions): what went wrong\n- Latency\n- Port insights: insights of what's going on during the execution of each of the use case's ports\n- IO data: what the use case execution received as input and what returned as output\n\nAn example for the structured format:\n\n```json\n{\n    \"useCaseExecution\": {\n        \"useCase\": \"auth_root_account_implementation\",\n        \"correlationId\": \"c6268eb5-2f2b-48e3-8138-f5636af216b4\",\n        \"successful\": \"true\",\n        \"exception\": null,\n        \"latency\": \"31\",\n        \"portInsights\": [\n            \"FindRootAccountByLoginIdPortAdapter's insights: no exception has been thrown\",\n            \"FindRootAccountSecretPortAdapter's insights: no exception has been thrown\",\n            \"SessionTokenGenerationPortAdapter's insights: no exception has been thrown\"\n        ],\n        \"io\": {\n            \"input\": {\n                \"loginId\": \"caertsc@capitolio.com\",\n                \"pass\": \"**********\"\n            },\n            \"output\": {\n                \"expiration\": \"3600\",\n                \"name\": \"Capitólio\",\n                \"id\": \"4\",\n                \"token\": \"eyJhbGciOiJIUzI1NiJ9.eyJvd25lciI6IjQiLCJhY3RvciI6IjQiLCJzY29wZXMiOiJST09UIiwiZXhwIjoxNzI5OTgyNDk4fQ.j068hm4oLFTFM2M5luS7UsB4YAEjYplkx1dAmubWVS8\"\n            }\n        }\n    }\n}\n```\n\nAs for the natural language style:\n\n```\nUse case \"auth_root_account_implementation\" execution with correlation ID of \"509cbcbe-c6e8-4ffa-9ca6-62b1cd35e2e3\" finished successfully. It took about 65 milliseconds. | Port insights: [FindRootAccountByLoginIdPortAdapter's insights: no exception has been thrown, FindRootAccountSecretPortAdapter's insights: no exception has been thrown, SessionTokenGenerationPortAdapter's insights: no exception has been thrown] [USE CASE INPUT]: { \"pass\": \"**********\", \"loginId\": \"caertsc@capitolio.com\" }; [USE CASE OUTPUT]: { \"expiration\": \"3600\", \"name\": \"Capitólio\", \"id\": \"4\", \"token\": \"eyJhbGciOiJIUzI1NiJ9.eyJvd25lciI6IjQiLCJhY3RvciI6IjQiLCJzY29wZXMiOiJST09UIiwiZXhwIjoxNzMwMjQ3NDExfQ.kYGx3I8KbwzzRk9znYb-r6_h58359QVQZTFwFB9ipl8\" };\n```\n\nThe IO data processing for inclusion into the log payload is done natively with a _to-json_ method. During its execution, the cae-native process takes into account the fields of the IO objects that are marked with the ```@Sensitive``` annotation. Depending on the parameterized configuration of this annotation, the autolog will:\n\n- Completely mask the field value\n- Partially mask the field value\n- Mask from right or from left\n- Just ignore the actual value and put a fixed length of \"*\" characters\n\n```java\n@Getter\n@Setter\npublic class SomeExample extends UseCaseInput {\n\n    @Sensitive(unmaskFromLeft = false, unmaskedAmount = 3)\n    private Long somePartiallyMaskedFieldFromRightToLeft;\n\n    @Sensitive(unmaskedAmount = 5)\n    private String anotherPartiallyMaskedFieldFromLeftToRight;\n\n    @Sensitive(defaultMaskedAmount = 8)\n    private String willJustBeAMaskWith8OfLength;\n\n}\n\n```\n\nAny exceptions thrown during the execution of a Use Case will be intercepted by the Use Case itself. If the exception is a subtype of ```MappedException```, the Use Case instance will consider it a part of the designed flow, as it is a ```MappedException```, and let it go untouched. On the other hand, if it is not, the Use Case instance will see it as an unexpected exception and wrap it into a ```UseCaseExecutionException``` object. Either way the autolog will include this event in the log data.\n\nThe framework itself doesn't have a dependency for an actual logger, it depends on the client application to provide an implementation of the ```Logger``` native interface:\n\n```java\npublic interface Logger {\n\n    void logInfo(String info);\n    void logError(String error);\n    void logDebug(String info);\n\n}\n```\n\nAn example of an actual implementation of the interface above, on the side of a client application:\n\n```java\n@NoArgsConstructor(access = AccessLevel.PRIVATE)\n@Slf4j\npublic class LoggerAdapter implements Logger {\n\n    public static final Logger SINGLETON = new LoggerAdapter();\n\n    @Override\n    public void logInfo(String info) {\n        log.info(info);\n    }\n\n    @Override\n    public void logError(String error) {\n        log.error(error);\n    }\n\n    @Override\n    public void logDebug(String info) {\n        log.debug(info);\n    }\n\n}\n```\n\nOnce an implementation of the ```Logger``` interface is created, to provide it to the framework, it goes like this:\n\n```java\nLoggerProvider.SINGLETON.setProvidedInstance(LoggerAdapter.SINGLETON);\n```\n\nThe ```LoggerProvider``` is a native component of the cae-framework. The ```LoggerProvider::setProvidedInstance``` will receive any implementation of the ```Logger``` interface.\n\nExpanding on the usage of the ```LoggerProvider``` API:\n\n- ```LoggerProvider::structuredFormat```: if set ```true```, the presentation style of the log payload is the JSON mentioned in the beginning of this section. If ```false```, in a simple text format.\n- ```LoggerProvider::setUseCasesLoggingIO```: another ```boolean``` for setting whether or not the autolog will include the IO data of Use Case executions.\n- ```LoggerProvider::setPortsLoggingIO```: same as the previous one, but for ```Ports``` (we'll get there).\n- ```LoggerProvider::setLoggingStackTrace```: whether or not the autolog will include logs of StackTrace for exceptions thrown during Use Case executions.\n- ```LoggerProvider::setNumberOfLinesFromStackTrace```: if the previous one is set ```true```, it is possible to set the number of StackTrace lines will be included into the log.\n- ```LoggerProvider::setIOLoggingMode```: whether to use the CAE Native mode (which converts objects to JSON) or to rely on the objects' ```toString``` implementations.\n\nIt will look like this:\n\n```java\nLoggerProvider.SINGLETON\n    .setProvidedInstance(LoggerAdapter.SINGLETON)\n    .setIOLoggingMode(IOLoggingMode.CAE_NATIVE)\n    .structuredFormat(false)\n    .setUseCasesLoggingIO(true)\n    .setPortsLoggingIO(false)\n    .setLoggingStackTrace(true)\n    .setNumberOfLinesFromStackTrace(2);\n```\n\n\u003cbr\u003e\n\n##### ⤵ Auto input validation\n\nTwo types of Use Case accept input: the ```FunctionUseCase``` and the ```ConsumerUseCase```. Since they do, it is desirable to have a way to establish required input fields as _not-null_, _not-blank_, _not-empty_, etc. The cae-framework supports all of these, natively:\n\n- ```@NotNullInputField```: for fields of any type that must not be null.\n- ```@NotBlankInputField```: for ```String``` fields which can't be blank (empty or all-space strings).\n- ```@NotEmptyInputField```: for ```String``` and ```Collection``` fields that cannot be empty.\n- ```@ValidInnerPropertiesInputField```: for custom types that, inside, have their own properties with their own validation rules, based on the annotations mentioned above.\n\nThe input validation rule is established when any field of a ```UseCaseInput``` subtype is annotated with one or more of the above annotations.\n\n\u003cbr\u003e\n\nAn example of ```UseCaseInput``` validation rule:\n\n```java\n@Getter\n@Setter\npublic class AuthRootAccountUseCaseInput extends UseCaseInput {\n\n    @NotNullInputField\n    @NotBlankInputField\n    private String loginId;\n\n    @NotNullInputField\n    @NotBlankInputField\n    @Sensitive\n    private String pass;\n\n}\n```\n\nThat way, whenever the ```AuthRootAccountUseCase``` instance gets executed and receives an ```AuthRootAccountUseCaseInput``` object as input, the Use Case will internally call the ```UseCaseInput::validateProperties``` API, which will ensure the validation rule is respected. If it is, the Use Case accepts the input and proceeds to process it. If it is not, the Use Case rejects and throws an exception specifying what went wrong:\n\n\u003cbr\u003e\n\n```\nField 'AuthRootAccountUseCaseInput:loginId' can't have blank values.\n```\n\n\u003cbr\u003e\n\n##### 📦 Autocache\n...\n\n\u003cbr\u003e\n\n##### 🔔 Autonotify\n...\n\n\u003cbr\u003e\n\n##### 🎯 Scope based authorization\nUse Case types annotated with the ```@ProtectedUseCase``` will need to specify, within the annotation, the required scopes for being granted the access to execute the Use Case instance.\n\n```java\n@ProtectedUseCase(scope = \"ROOT || MAINTAINER\")\npublic abstract class CreateUserAccountUseCase extends FunctionUseCase\u003c\n        CreateUserAccountUseCaseInput,\n        CreateUserAccountUseCaseOutput\u003e {}\n```\n\nFor the Use Case above (```CreateUserAccountUseCase```), it is necessary to have either the _ROOT_ or the _MAINTAINER_ scope in order to execute it. The way the framework knows whether or not the responsible for the execution has the required scopes is via the ```Actor``` interface.\n\n```java\npublic interface Actor {\n    List\u003cString\u003e getScopes();\n}\n```\n\nAn example for an actual implementation of it, on the side of a client application:\n\n```java\n@RequiredArgsConstructor(access = AccessLevel.PRIVATE)\npublic class ActorSessionManager implements Actor {\n\n    @Getter\n    private final String owner;\n    @Getter\n    private final String id;\n    private final String scopes;\n\n    public static Actor createOutta(String authorizationHeader){\n        var jwt = JWT.of(authorizationHeader.replace(\"Bearer \", \"\"));\n        var claims = jwt.getDecryptedJWT();\n        if (claims.getExpiration().before(new Date()))\n            throw new UnauthorizedException();\n        return new ActorSessionManager(\n                claims.getOwner(),\n                claims.getActor(),\n                claims.getScopes()\n        );\n    }\n\n    @Override\n    public List\u003cString\u003e getScopes() {\n        return List.of(this.scopes);\n    }\n```\n\nThe example above extracts the expected ```scopes``` out of a JWT.\n\nOnce a concrete implementation of the ```Actor``` interface is created, the way to provide its instances on each Use Case execution is via the ```ExecutionContext``` object. For Use Case types which aren't annotated with ```@ProtectedUseCase```, the ```ExecutionContext``` instance provided in each execution isn't required to have an instance of the ```Actor``` interface, however, for protected Use Case types, if one is not provided, the execution will be rejected.\n\nThe way to provide an instance of ```Actor``` to the ```ExecutionContext``` is as follows, considering the example of ```ActorSessionManager``` mentioned lastly as the implementation:\n\n```java\nvar actor = ActorSessionManager.createOutta(authorization);\nvar executionContext = ExecutionContext.of(correlationId, actor);\nvar useCaseOutput = useCase.execute(useCaseInput, executionContext);\n```\n\n\u003cbr\u003e\n\n##### ⛑️ Role based authorization\n...\n\n\u003cbr\u003e\n\n### 📖 Autodoc\nDuring the build phase of your application, each Use Case has its metadata extracted to a file named ```cae-docfile.json```. This is an autodocumentation that can be integrated with the ```CAE Real-Time Service Catalog``` SaaS which is on its way to be born. The concept is that during CI/CD pipelines a Service Catalog is kept up to date in real time, enabling teams across the organization to keep up with what's available. \n\nThe autodoc feature doesn't care whether your Use Case instances are dispatched as REST Endpoints, Kafka Consumers, SQS Listeners, CRON jobs or any other type of primary adapter flavour: it only has eyes for the Use Cases themselves, which means a single way to document all Use Cases in a seamless manner.\n\n\n## 🌐 Other components of the SDK:\n\n- ``✔️`` [cae-cli](https://github.com/clean-arch-enablers-project/cae-cli)\n- ``✔️`` [cae-utils-mapped-exceptions](https://github.com/clean-arch-enablers-project/cae-utils-mapped-exceptions)\n- ``✔️`` [cae-utils-http-client](https://github.com/clean-arch-enablers-project/cae-utils-http-client)\n- ``✔️`` [cae-common-primary-adapters](https://github.com/clean-arch-enablers-project/cae-common-primary-adapters)\n- ``✔️`` [cae-utils-env-vars](https://github.com/clean-arch-enablers-project/cae-utils-env-vars)\n- ``✔️`` [cae-utils-trier](https://github.com/clean-arch-enablers-project/cae-utils-trier)\n- ``✔️`` [cae-rdb](https://github.com/clean-arch-enablers-project/cae-rdb)\n- ``⏳`` [cae-service-catalog](https://github.com/clean-arch-enablers-project/cae-service-catalog)\n\n\u003cbr\u003e\n\u003cbr\u003e\n\u003cbr\u003e\n\u003cbr\u003e\n\n\u003cp align=\"center\"\u003e\n  CAE — Clean Architecture made easy.\n\u003c/p\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclean-arch-enablers-project%2Fcae-framework","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fclean-arch-enablers-project%2Fcae-framework","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclean-arch-enablers-project%2Fcae-framework/lists"}