Crnk Framework

repository·master·Indexed 43 days ago

https://github.com/crnk-project/crnk-framework

A Java framework that implements the JSON API specification to simplify the creation of RESTful web services. It provides high-level abstractions for pagination, filtering, and complex data fetching, with deep integration for Spring, JPA, Dropwizard, Jersey, WildFly, and Open Liberty. Includes the @crnk/angular-ngrx helper library for bridging NgRx state management with JSON:API backends.

Tokens
27.2K
Snippets
71
Records
129
Agent score
52%

What's inside Crnk

  1. Overview of @crnk/angular-ngrx components

    master

    The @crnk/angular-ngrx package provides several specialized modules to integrate Angular components with ngrx-json-api and JSON:API resources:

    • @crnk/angular-ngrx/operations: Implements JSON PATCH support via CrnkOperationsModule, enabling bulk insert, update, and delete capabilities by replacing standard ApiApplyInitAction implementations with single bulk requests.
    • @crnk/angular-ngrx/expression: Provides a type-safe, QueryDSL-like expression model for TypeScript to address the boilerplate of binding JSON:API resource fields to Angular components.
    • @crnk/angular-ngrx/expression/forms: Binds the expression model to Angular form components (a JSON:API specific flavor of ngModel).
    • @crnk/angular-ngrx/binding: Contains helper classes like FormBinding and DataTableBinding to connect tables and forms to JSON:API resources.
    • @crnk/angular-ngrx/meta: Provides the TypeScript API for the Meta Module generated by crnk-gen-typescript.
    • @crnk/angular-ngrx/stub: Contains minor base classes used by the TypeScript generator.
  2. What is Crnk?

    master

    Crnk is a native resource-oriented REST library designed to accelerate the development of RESTful applications. Unlike traditional REST libraries, Crnk focuses on resources, their relationships, and repositories as the primary building blocks. This approach allows developers to focus on application logic rather than low-level protocol details.

    Key capabilities include:

    • Standardized URL handling: Supports patterns like /api/persons?filter[title]=John and /api/persons/{id}.
    • Advanced Querying: Built-in support for sorting, filtering, pagination, and sparse field sets (partial objects).
    • Complex Data Graphs: Supports requesting complex object graphs in a single request using JSON:API inclusions.
    • Resource Operations: Supports inserting, updating, deleting, and atomic operations via jsonpatch.com.
    • Metadata & Links: Ability to attach link and meta information to resources.
    • Extensibility: A flexible module API to choose and extend features, along with filters and decorators to intercept and modify application aspects.
  3. Overview of @crnk/angular-ngrx

    master

    The @crnk/angular-ngrx package is an Angular helper library designed to work with ngrx-json-api and crnk. It provides specialized bindings and tools to streamline the development of RESTful applications in Angular by bridging the gap between NgRx state management and the Crnk/JSON:API backend.

    Key features include:

    • Form bindings: Simplifies connecting Angular forms to JSON:API resources.
    • Table bindings: Provides integration for displaying resource collections in tables.
    • QueryDSL-like expression API: A powerful alternative to ngModel for constructing complex queries.
    • Bulk update support: Leverages JSON Patch for efficient batch updates.
    • TypeScript interfaces: Provides type safety for the Crnk meta model.
  4. Explore Crnk integration examples

    master

    The crnk-integration-examples repository contains various example applications that demonstrate how to integrate Crnk into different frameworks. These examples are primarily focused on setup-related tasks and configuration.

    For a comprehensive, main example application based on Spring Boot that showcases the full use of the Crnk API, refer to the crnk-example repository.

  5. How Expressions and Paths work in @crnk/angular-ngrx

    master

    The @crnk/angular-ngrx/expression module solves the problem of binding Angular components to complex JSON:API resources. While ngModel only handles simple values, Crnk Expressions allow components to understand the full resource and the specific path to a field, which is necessary for proper validation and error mapping.

    Key abstractions:

    • Expression: An interface representing any value obtainable from a resource.
    • Path<T>: Implements Expression and refers to a property of type <T> in an object.
    • BeanBinding: Implements Path and represents the root resource (an empty path).
    • Type-safe implementations like StringPath, NumberPath, BooleanPath, and BeanPath<T> are used to handle primitives and objects.

    Commonly used methods on expressions:

    • getValue(): Fetches the value at the given path.
    • setValue(): Sets the value at the given path.
    • toFormName(): Computes a unique form name (composed of resource type, ID, and path) to allow multiple resources to be edited on one screen.
    • toQueryPath(): Constructs a path for sorting/filtering that excludes JSON:API structural elements like attributes or relationships.
    <!-- Standalone expression binding -->
    <input id="nameInput" [crnkExpression="resource.attributes.name"/>
    
    <!-- Form binding (registers with ngForm using toFormName) -->
    <input id="nameInput" required [crnkFormExpression="resource.attributes.name"/>
  6. Core building blocks: Resources, Relationships, and Repositories

    master

    Crnk is built around three central abstractions that form a resource-oriented (graph-based) API model:

    • Resources: These hold the actual data, including value attributes, meta-information, and link information.
    • Relationships: These establish links between different resources.
    • Repositories: resource repositories and relationship repositories implement the actual data access logic. You interact with these repositories using standard HTTP methods: GET, POST, PATCH, and DELETE.

    Instead of using RPC-style services (e.g., a changeAddress service), Crnk encourages modeling actions as resource manipulations, such as PATCHing a relationship or POSTing to a dedicated change resource.

  7. Create repositories dynamically at runtime

    master

    If you cannot define repositories at compile-time, use one of these strategies:

    1. Jackson Annotations: Use @JsonAnyGetter and @JsonAnySetter on resource classes to allow dynamic properties. Combine this with the ResourceTypeHolder interface to manipulate resource types.
    2. Generic Resource Class: Use the Resource class, which is the generic JSON:API representation used by the Crnk engine.
    3. Map-based Resources: Use a java.util.Map and provide a ResourceFieldAccessor for each ResourceField to define how to read/write attributes.

    To register these dynamic repositories, you can either:

    • Use a Module to call ModuleContext.addRegistryEntry (best for a fixed set of dynamic repositories).
    • Implement ResourceRegistryPart and call ModuleContext.addResourceRegistry (best for fully dynamic sets, like database tables that change over time).
  8. Implement Nested Resources (Experimental)

    master

    Nested resources allow URLs to represent hierarchies, such as http://example.com/posts/1/comments/2.

    Single Nested Resources

    These share the same identifier as the parent.

    • Set nested = true on the resource.
    • Use @JsonApiRelationId to point to the parent ID.

    Multiple Nested Resources

    These use a structured identifier consisting of two parts:

    1. The local identifier (annotated with @JsonApiId) which must be unique among siblings.
    2. The parent identifier (annotated with @JsonApiRelationId) which links to the parent resource.

    Note: Nesting is currently limited to a single level.

  9. Use QuerySpec for filtering, sorting, and paging

    master

    Crnk passes JSON:API query parameters to repositories via a QuerySpec object. This object encapsulates request parameters like sorting, filtering, and paging.

    Common Spec classes used within QuerySpec:

    • FilterSpec: Represents attribute paths, operators, and values.
    • SortSpec: Represents sorting directions and paths.
    • PagingSpec: Represents offset/limit or number/size paging.
    • IncludeFieldSpec: Represents requested field sets.
    • IncludeRelationSpec: Represents requested resource inclusions.

    QuerySpec also provides an apply(Iterable<T> resources) method, which allows for in-memory sorting, filtering, and paging on any java.util.Collection. This is highly useful for testing or working with small datasets.

    public class QuerySpec {
    	public <T> List<T> apply(Iterable<T> resources){...}
    	public Long getLimit() {...}
    	public long getOffset() {...}
    	public PagingSpec getPagingSpec() {...}
    	public List<FilterSpec> getFilters() {...}
    	public List<SortSpec> getSort() {...}
    	public List<IncludeFieldSpec> getIncludedFields() {...}
    	public List<IncludeRelationSpec> getIncludedRelations() {...}
    	public QuerySpec getQuerySpec(Class<?> resourceClass) {...}
    	...
    }
  10. Use Plain JSON format instead of JSON:API

    master

    While Crnk follows the JSON:API specification by default, you can use a simpler, non-JSON:API format via the crnk-format-plain-json module. This module provides the PlainJsonFormatModule, which allows for a flatter resource structure by removing the attributes and relationships containers and inlining relationships directly within the resource.

    The PlainJsonFormatModule can be applied to both CrnkBoot (server-side) and CrnkClient (client-side). The format is selected using the HTTP Accept and Content-Type headers, meaning the standard JSON:API endpoint remains functional and accessible.

    {
      "data" : {
        "id" : "12",
        "type" : "tasks",
        "name" : "someTask",
        "schedule" : {
          "data" : null,
          "links" : {
            "self" : "http://localhost:8080/tasks/12/relationships/schedule",
            "related" : "http://localhost:8080/tasks/12/schedule"
          }
        },
        "project" : {
          "data" : {
            "id" : "1",
            "type" : "projects",
            "name" : "someProject"
          },
          "links" : {
            "self" : "http://localhost:8080/tasks/12/relationships/project",
            "related" : "http://localhost:8080/tasks/12/project"
          }
        }
      }
    }
  11. Integrate authentication using SecurityProvider

    master

    Crnk is agnostic to the specific authentication scheme (OAuth, JWT, SAML, etc.) and typically relies on the underlying integration (like JEE Principal or Spring Security SecurityContext) to identify the user.

    To bridge these systems with Crnk, you use the SecurityProvider interface. The JAX-RS and servlet integrations include built-in implementations of SecurityProvider that work with Spring and other derived frameworks. The SecurityProvider is used to check user roles and perform both DAC and RBAC checks.

  12. Use Resource Versioning (Experimental)

    master

    Crnk supports versioning via content negotiation using the HTTP Accept header or a URL parameter. This allows you to evolve your API without changing URLs.

    How it works:

    • Annotate resources and fields with @JsonApiVersion to specify a version range.
    • Crnk will only include resources/fields if their version range intersects with the requested version.
    • Versions are integer-based (representing major versions).

    Requesting a version:

    Via Accept Header: GET /api/versionedTask HTTP/1.1
    Accept: application/vnd.api+json; version=2

    Via URL Parameter: GET /api/versionedTask?version=2 HTTP/1.1

    Limitations:

    • ID fields cannot be versioned.
    • Meta and link fields cannot be versioned (currently).
    • You cannot use multiple classes to serve the same resource for different version ranges; instead, redefine fields within the class.
    @JsonApiResource(type = "versionedTask")
    public class VersionedTask {
      @JsonApiVersion(min = 1, max = 3)
      private boolean completed;
    
      @JsonApiVersion(min = 4)
      @JsonProperty("completed")
      private CompletionStatus newCompleted;
    }