Fklang Specification

Fklang provide a two-way binding between design-implementation for architecture.

Basic Works:

  • DDD syntax. DDD strategy and tactic description.
  • DomainEvent Implementation. for generate implementation of DomainEvent.
  • Binding. mapping DSL to SourceCode
  • Layered syntax. layered structured syntax.

In dev:

  • Env for Database.
  • SourceSet Plugin. third-part integration, like PlantUml, Swagger.

Bootstrapping:

  • Builtin Types. like Context, Container, or else.
  • Description syntax. description design in fake code.
  • Typedef (TBD). for DDD syntax type bootstrapping.

Basic Syntax

关键词基本命名规则:

  • DDD 关键词以大写开头,驼峰式命名
  • 其他以小写开头,避免驼峰
  • 特殊场景,遵循该领域的命名规则(但是,应该支持全小写)
    • 例外场景 1:HTTP 请求方法,使用全大写

assign

attr_type: attr_value;

example:

request: CinemaUpdatedRequest;

declare

Keyword IDENTIFIER (COMMA  IDENTIFIER)*;

example

Aggregate Ticket {
  Entity Ticket, Seat;
}

DDD

DDD Syntax:

declusage
context_map_decl:[ 'ContextMap' ] [ ID ] '{' (context_node_decl | context_node_rel ) '}'
|att_list
context_node_decl:['context'] [ID]
context_node_rel:[ ID ] rel_symbol [ ID ]
rel_symbol:('->' | '<-' | '<->')
context_decl:[ 'Context' ] [ ID ] '{' aggregate_list? '}'
|att_list
att_list:attr_item+
attr_item:([ ID ] '=' [ value ] ','?)* ';'?
|([ ID ] ':' [ value ] ','?)* ';'?
|[ ID ] ([ value, ',' ])* ';'?
aggregate_decl:[ 'Aggregate' ] [ ID ] '{' entity_list '}'
|att_list
entity_decl:[ 'Entity' ] [ ID ] '{' value_object_list '}'
|att_list
value_object__decl:[ 'ValueObject' ] [ ID ] '{' value_list '}'
|att_list

Sample

ContextMap Ticket {}

Context ShoppingCarContext {}

// render wtih UML styled?
Aggregate Cart {
  """ inline doc sample
  just-demo for test
  """
  DomainEvent CartCreated, CartItemAdded, CartItemRemoved, CartItemQuantityChanged, CartCheckedOut;
  DomainEvent CartItemQuantityChanged;

  Entity Cart;
}

Entity Cart {
  // it's to many, can change in different way.
  ValueObject CartId
  ValueObject CartStatus
  ValueObject CartItem
  ValueObject CartItemQuantity
  ValueObject CartItemPrice
  ValueObject CartItemTotal
  ValueObject CartTotal
}

DomainEvent Implementation

Subscribe / Publish / Event / Flow

API

  • input -> request
    • pre-validate
  • output -> response
    • post-validate
  • process -> flow
    • tasking

compare to given-when-then.

impl CinemaCreated {
  endpoint {
    POST "${uri}/post";
    request: Request;
    authorization: Basic "{{username}}" "{{password}}";
  }
  
  // created in ApplicationService
  flow {
    via UserRepository::getUserById() receive user: User
    // send "book.created" to Kafka
    via UserRepository::saveUser(user: User) receive void
    // or
    via UserRepository::save(user: User) receive user: User;
    // message queue
    via MessageQueue send CinemaCreated to "CinemaCreated"
    // http request
    via HTTP::post() send Message to "${uri}/post"
    // grpc Greeter
    via GRPC::Greeter send CinemaCreated to "CinemaCreated"
    // map filter
    when(isUserValid) {
      is true => {
        // do something
      }
      is false => {
        // do something
      }
    } 
  }
}

expect generate code will be:

// get_user_by_user_id from JPA
public User getUserByUserId(String userId) {
  return userRepository.findByUserId(userId);
}

// get_user_by_user_id from MyBatis
public User getUserByUserId(String userId) {
  return userMapper.getUserByUserId(userId);
}

Binding

Binding provide a way to binding source code to Context

Aggregate Ticket {
  DomainEvent TicketCreated, TicketUpdated, TicketDeleted;
}

// or to service ?
impl TicketBinding {
  aggregate: Ticket; 
  endpoint {
    GET "/ticket/{id}";
    request: GetTicketRequest;
    authorization: Basic admin admin;
    response: Ticket;
  }
}

//define config: ExtraConfig {
//  baseUrl: "/ticket";
//  language: "Java";
//  package: "com.phodal.coco";
//}

If no config, will use default config by scanner?

SourceSet (TBD)

SourceSet is design for support 3rd-party dsl, like PlantUML, Swagger.yaml

declusage
source_set_decl:simple_source_set_decl
|space_source_set_decl
space_source_set_decl:[ 'SourceSet' ] [ ID ] '{' att_list '}'
simple_source_set_decl:[ 'SourceSet' ] [ ID ] '(' att_list ')'
implementation_decl:[ 'impl' ] [ID] '{' (inline_doc) '}'

PlantUML for Structure

file_type: uml, puml

SourceSet sourceSet {
  feakin {
    srcDir: ["src/main/resources/uml"]
  }
  puml {
    parser: "PlantUML"
    srcDir: ["src/main/resources/uml"]
  }
}

Swagger API (TBD)

file_type: Yaml, JSON

with: XPath

refs:

//

SourceSet petSwagger {
  swagger {
    parser: "Swagger"
    srcDir: ["src/main/resources/swagger"]
    xpath: "/definitions/Pet"
  }
}

// with XPath

UniqueLanguage model ? (TBD)

file_type: CSV, JSON, Markdown ?

SourceSet TicketLang {
  UniqueLanguage {
    srcDir: "ticket.csv";
    type: UniqueLanguage;
    prefix: "Ticket";
  }
}

Layered

Layered is design for decl

declusage
layered_decl:'layered' ([ ID ] | 'default' ) '{' layered_body? '}'
layered_body:layer_dependency
|layer_item_decl
layer_item_decl:'layer' [ ID ] '{' layer_item_entry* '}'
layer_item_entry:package_decl
package_decl:'package' ':' [ string ]

can be guarding for model

layered DDD {
  dependency {
    "interface" -> "application"
    "interface" -> "domain"
    "domain" -> "application"
    "application" -> "infrastructure"
    "interface" -> "infrastructure"
  }
  layer interface {
     package: "com.example.book"
  }
  layer domain {
     package: "com.example.domain"
  }
  layer application {
    package: "com.example.application"
  }
  layer infrastructure {
    package: "com.example.infrastructure"
  }
}

Env

For database and mock server

env Local {
  datasource {
    driver: postgresql
    host: "localhost"
    port: 5432
    database: "test"
  }
  server {
    port: 9090;
  }
}

with API testing (Todo)

with Help utils function

  • builtin-functions: mock server
  • builtin-functions: verify server for testing contract
impl CinemaCreated {
  endpoint {
    GET "/book/{id}";
    request: CreateBookRequest;
    authorization: Basic admin admin;
    response: Cinema;

    // a mock server for testing
    mock {
       port: 8080;
    };
    verify {
       env: Local;
       expect {
        "status": 200
        "data": {
          // build in APIs ?
          "id": {{$uuid}};
          "price": {{$randomInt}};
          "ts": {{$timestamp}};
          "value": "content"
        }
    }
  }

  // full processing (TBD)
  request CreateBookRequest {
//    schema => data,
//    schema {
//      "title" : "string",
//      "author" : "string",
//      "price" : "number"
//    }
    data {
      "title" : "The Lord of the Rings",
      "author" : "J.R.R. Tolkien",
      "price" : 29.99
    }
    validate {
      // title.length > 10 ? 
      title  {
        required { min: 3, max: 10 }
        pattern { regex: "^[a-zA-Z0-9]+$" }
        range { min: 1, max: 100 }
      }
    } 
  } 
  
  middle {
    via User get/update/delete/post userId 
    via Kafka send "book.created"
  }

  response CreateBookResponse {
     struct {
        "id" : "number"
     }
     validate  { }
  } 
  
  // with source side (TBD)
  output CreateBookResponse(xpath="");
  input CreateBookResponse(sourceSet="PetSwagger" location="");
}


env Local {
  host: "http://localhost:8080";
}

Default impl config (TBD)

var config: Config {
  language: "feakin"
  framework: "Spring"
  message: "Kafka" 
  dao: "JPA"
  cache: "Redis"
  search: "ElasticSearch"
}

Bootstrapping

Typedef (TBD)

Typedef provide custom syntax like container or others, can support for bootstrapping DDD syntax.

BuildIn Types

Basic Types

NameDescription
identifierunique identifier
binaryAny binary data
bitsA set of bits or flags
boolean"true" or "false"
enumerationEnumerated strings
stringstring
numberAny number, can be float or int
optional ?Optional type ?

Data Types ?

NameDescription
TableTable data
ListList data
MapMap data
SetSet data
TupleTuple data
ObjectObject data
ArrayArray data
DateDate data
TimeTime data
DateTimeDateTime data
DurationDuration data
IntervalInterval data

Variable

var source: JavaSource {
  language: "Java";
  package: "com.phodal.coco";
}

Container

def ContextMap {
    // todo: parser generator    
}
declusage
typedef_decl:[ 'typedef'] '(' metaType ')' ID '{' (decl_list) '}';
decl_list:decl_item*
decl_item:[ID] ':' decl_name

Expression

Description Syntax:

declusage
description_decl:[ ID ] '{' expr* '}'
expr:if_expr
|choose_expr
|behavior_expr
if_expr:[ 'if' ] '(' [ expression ] ')'
when_expr:[ 'when' ] '(' [ expression ] ')'
behavior_expr:['via'] [ ID ] action [ID]
action:[ 'get' | 'update' | 'delete' | 'create' | 'send']
var sample: FakeCode {
  // if (and ?) then {} else {}
  when(condition) {
    is condition1 {

    }
    is condition2 {

    }
    else {

    }
  }

  done
  operator: <, >, >=, <=, ==, +, -, *, %, /, ?
  // call
  via Entity send/ receive Event;
}

Function (Bootstrapping)

  • mock
  • verify
  • validate
mock {

}

func mock(container: MockContainer) {
  // steps
}

fun verify(input: Input, output: Output) {
  // steps
}

fun validate(input: Input, output: Output) {
  // steps
}