kind 0.2.0 kind: ^0.2.0 copied to clipboard
A data modeling framework. Easy serialization and observation object graphs. The API is usable without code generation. Includes database primitives such as GeoPoint and UUID.
Overview #
This is Kind framework for serialization / persistence / reactive state management.
This is an initial experimental version. The APIs are not frozen.
What it gives you? #
- Convert graphs to/from JSON trees.
- Convert graphs to/from Protocol Buffers (and GRPC) trees.
- Use databases (upcoming).
- Our sibling package database will use this framework in future.
- Reactive programming
- The package has ReactiveSystem for observing views and mutations of reactive states in the isolate. JSON / Protobuf deserialization methods construct reactive objects by default.
- Various small helpers for data layer programming.
- Use kind.instanceValidate to
validate instances. For instance, if you have
StringKind(minLengthInRunes:3, maxLines:1)
, you get a debugging-friendly error message when you validate instance "a". - Use kind.randomExample to generate random instances of the kind.
- Frameworks may use kind.newList
to construct a memory-efficient lists without knowing the type. For instance,
Float32Kind, will
give you a dart:typed_data Float32List
rather than normal
List<double>
when you ask for a non-growable list. - Frameworks may use entityKind.meaning to understand kinds and properties in terms of other vocabularies (such as schema.org schemas).
- Use kind.instanceValidate to
validate instances. For instance, if you have
Overview of APIs #
Built-in kinds #
- Booleans
- Integers
- Floating-point numbers
- Date and time
- Strings and bytes
- Lists and sets
- Others
- Your custom models
Built-in string formats #
- stringKindForEmailAddress (email address)
- stringKindForMarkdown (Markdown formatted content)
- stringKindForPhoneNumber (phone number)
- stringKindForUrl (URL)
Data primitives #
- Date (like DateTime, but does not define time)
- DateTimeWithTimeZone (like DateTime, but allows any time zone)
- GeoPoint (geographical latitude/longitude coordinates)
- Uuid (128-bit object identifier)
Reactive collection classes #
Some alternatives #
For serialization #
- built_value
- Generates code for immutable values and JSON serialization.
- json_serializable
- Generates code for JSON serialization.
- protobuf
- Generates code for Protocol Buffers serialization.
For state management #
Getting started #
1.Adding dependency #
In pubspec.yaml, you should have something like:
environment:
sdk: '>=2.12.0-0 <3.0.0'
dependencies:
kind: ^0.2.0
2.Write data models #
In this example, we use Field helper to avoid writing reactive getters / setters:
class Person extends Entity {
static final EntityKind<Person> kind = EntityKind<Person>(
name: 'Person',
build: (b) {
b.optionalString(
id: 1,
name: 'fullName',
minLength: 1,
field: (e) => e.fullName,
);
b.requiredSet<Person>(
id: 2,
name: 'friends',
itemsKind: Person.kind,
field: (e) => e.friends,
);
b.constructor = () => Person();
},
);
/// Full name.
late final Field<String?> fullName = Field<String?>(this);
/// Friends.
late final SetField<Person> friends = SetField<Person>(this);
@override
EntityKind getKind() => kind;
}
Serialization #
JSON #
// Encode
final json = person.getKind().jsonTreeEncode(person);
// Decode
final person = Person.kind.jsonTreeDecode(json);
Protocol Buffers #
// Encode
final generatedMessage = person.getKind().protobufTreeEncode(person);
// Decode
final person = Person.kind.protobufTreeDecode(generatedMessage);
Alternative approaches to specifying data classes #
Why / why not? #
The alternative approaches:
- Do not force you to deviate from the way you normally write classes.
- Perform better when you have millions of objects.
- Do not support reactive programming with
ReactiveSystem
unless you write a lot error-prone boilerplate code.- In future, we may release a code generator that generates boilerplate for you, but there will inevitably going to be some complexity unless Dart language designers decide to support something like decorator annotations.
For mutable classes #
...without reactive programming support #
You just define getter
and setter
in Prop
for ordinary Dart fields:
class Person {
/// Full name.
String? fullName = '';
/// Friends.
final Set<Person> friends = {};
}
/// EntityKind for [Person].
final EntityKind<Person> personKind = EntityKind<Person>(
name: 'Person',
build: (builder) {
builder.optionalString(
id: 1,
name: 'fullName',
getter: (t) => t.fullName,
setter: (t,v) => t.fullName = v,
);
builder.requiredSet<Person>(
id: 2,
name: 'friends',
itemsKind: personKind,
getter: (t) => t.friends,
);
builder.constructor = () => Person();
},
);
...with reactive programming support #
You can use ReactiveMixin for implementing reactive getters and setters:
class Person extends Entity with ReactiveMixin {
String? _fullName;
final Set<Person> _friends = ReactiveSet<Person>();
/// Full name of the person.
String? get fullName => beforeGet(_fullName);
set fullName(String? value) => _fullName = beforeSet(_fullName, value);
/// Friends of the person.
Set<Person> get friends => beforeGet(_friends);
@override
EntityKind<Person> getKind() => personKind;
}
// The `personKind` is identical to the previous example.
// ...
For immutable classes #
...without reactive programming support #
// Extending Entity is optional, but recommended.
class Person {
/// Full name of the person.
final String? name;
/// Friends of the person.
final Set<Person> friends;
Person({
this.fullName,
this.friends = const {},
});
}
/// EntityKind for [Person].
final EntityKind<Person> personKind = EntityKind<Person>(
name: 'Person',
build: (builder) {
final fullName = builder.optionalString(
id: 1,
name: 'fullName',
getter: (t) => t.fullName,
);
final friends = builder.requiredSet<Person>(
id: 2,
name: 'friends',
itemsKind: personKind,
getter: (t) => t.friends,
);
builder.constructorFromData = (data) {
return Person(
name: data.get(fullName),
friends: data.get(friends),
);
};
},
);
...with reactive programming support #
You can use ReactiveMixin for implementing reactive getters:
// Extending Entity is optional, but recommended.
class Person extends Entity with ReactiveMixin {
final String? _fullName;
final Set<Person> _friends;
/// Full name of the person.
String? get fullName => beforeGet(_fullName);
/// Friends of the person.
Set<Person> get friends => beforeGet(_friends);
Person({
required String? name,
Set<Person> friends = const {},
}) :
_fullName = name,
_friends = ReactiveSet<Person>.wrap(friends);
@override
EntityKind<Person> getKind() => personKind;
}
// The `personKind` is identical to the previous example.
// ...