dorm 1.0.42

  • README.md
  • Example
  • Installing
  • Versions
  • 68

Dorm

Dorm is a client side library, it can he hooked up to a server side ORM implementation such as Hibernate for example. The idea behind Dorm is that you expose your ORM entity model (or a subset of it) to your client application and load and/or commit entities asynchronously via services.

Dorm is used and tested in a production application, the server-side implementation is not open sourced. To set it up for example between Java Hibernate and 🎯 is not difficult however.

The entities implement the Observable library to provide easy binding into frameworks such as Polymer.

Communication with the server currently works via JSON, both for sending and receiving entities, but one can easily implement other message formats. An Entity has readExternal and writeExternal methods which will iterate all values and get or insert them to or from a Map object. This Map can them be converted to a message format of choice.

Dorm supports :

  • cyclic references (i.e. foo.bar.listOfFoos) via pointers
  • default serializer for JSON data
  • Entities use the observe library, an Entity extends ObservableBase, and an Entity collection is an ObservableList

Dorm will soon support :

  • push via web sockets, share Entity status between clients

Build Status Stories in Ready

Alt text <img src="http://igindo.com/dart/dorm/dorm_graph.svg">

Try It Now

Add the Dorm package to your pubspec.yaml file:

dependencies:
  dorm: any

Setting it up

The example in Dorm requires a Dart server which acts as a minimal ORM engine, running on JSON files. Here, you can define your database in the dbo folder using JSON.

The example is a good starting point for implementing Dorm yourself, it also has a few sample services and even a commit service.

When ready, run 'build_entities.dart' which will generate the client side entities needed in the dorm example.

Finally, run 'run_mock_server.dart' to launch the test server.

Now you can launch the 'dorm.dart' file in the main examples folder.

I've successfully hooked Dorm up with an existing Hibernate ORM, if you wish to do the same, then you must provide 2 things on the server :

  • a way to generate Dorm entities as in the example JSON test server, from your ORM engine
  • a serializer which supports cyclic references, Dorm can then access your webserver and request the JSON data

Generating Dorm entities

Dorm entities support inheritance.

Dorm entities use a proxy, you should generate Entity properties in the following way:

	// A declaration of an ID field called 'bar' and of type Bar
	
	@Property(BAR_SYMBOL, 'bar', Bar) // Bar being another [Entity]
	@Id() // Indicates that this property is an identity field, you can have multiple Id fields for combined key support
	@NotNullable() // We need a value when persisting this [Entity]
	@DefaultValue(const Bar('A null Bar')) // the default value when creating a new Bar();
	
	final DormProxy<Bar> _bar = new DormProxy<Bar>(BAR, BAR_SYMBOL); // the actual value is proxied
	
	static const String BAR = 'bar';
	static const Symbol BAR_SYMBOL = const Symbol('orm_domain.TestEntity.bar'); // full path + prop name
	
	Bar get bar => _bar.value;
	set bar(Bar value) => _bar.value = notifyPropertyChange(BAR_SYMBOL, _bar.value, value);

Then, in the Entity constructor body, you need to register this property as following:

	Entity.ASSEMBLER.registerProxies(
      this,
      <DormProxy>[_bar, /*plus any other properties*/]
    );

See Person for a full example of a Dorm entity.

Finally, you must also define a library file which contains all your generated entities,

with this file, generate the following method:

void ormInitialize() {
	EntityAssembler assembler = new EntityAssembler();

	assembler.scan(TestEntity, 'entities.TestEntity', TestEntity.construct);
	//... other Entities...
}

And in your project main(), call this method to initialize Dorm.

Serialization

By default, Dorm uses a JSON serializer, but you could write your own serializer to support AMF for example.

Your client services must use the serializer when dealing with incoming/outgoing data, here's an example of a service's body:

	final Serializer serializer = new SerializerJson();
	
	Future serviceMethodNameHere(String operation, Map<String, dynamic> arguments) {
		Completer completer = new Completer();
		
		HttpRequest.request(
			'http://${host}:$port', 
			method:operation, 
			sendData:serializer.outgoing(arguments) // serialize your arguments to JSON
		).then(
			(HttpRequest request) {
			  if (request.responseText.length > 0) {
				EntityFactory<Entity> factory = new EntityFactory(onConflict);
				
				List<Map<String, dynamic>> result = serializer.incoming(request.responseText); // parses the incomin JSON data to a Map
				
				ObservableList<Entity> spawned = factory.spawn(result, serializer); // generates your entities from the above Map
				
				completer.complete(isUniqueResult ? spawned.first : spawned);
			  }
			}
		);
		
		return completer.future;
	}

The JSON serializer only handles the basic data types (numerics, String, basic Lists and Maps, ...) To support other types, you can set type handlers to the serializer as following :

	serializer.addRule(
      DateTime,
      (int value) => (value != null) ? new DateTime.fromMillisecondsSinceEpoch(value, isUtc:true) : null,
      (DateTime value) => value.millisecondsSinceEpoch
	);

This rule will read the date as int value from the incoming JSON data, and send out again the date as an int whenever the data is outgoing.

When Dorm submits changes to its entities, only properties that are dirty (changed on the client) will be serialized.

Serialization rules

Dorm needs to understand the serialized JSON when it comes to cyclic references, one way for the server to support this, is by creating DTO's for your entities, then when the serialization is needed, loop over the DTO and in doing so, keep a reference to the entities that are already serialized.

If a cyclic reference is detected, then instead of serializing the DTO values, do this instead:

	{
		"?t":"path.to.entities.Foo", // the entity type, including the class path
		"?p":true, // indicates that this is a cyclic reference
		"foo_id":1 // primary key name and value
	}

example/dorm.dart

import 'dart:convert';
import 'dart:html';

import 'package:dart_flex/dart_flex.dart';
import 'package:dorm/dorm.dart';
import 'package:observe/observe.dart';

import 'orm_domain/orm_domain.dart';
import 'orm_infrastructure/orm_infrastructure.dart';

final String url = '127.0.0.1';
final String port = '8080';
final Serializer serializer = new SerializerJson();

FetchService fetchService;
CommitService commitService;
FetchService postCommitFetchService;

ConflictManager handleConflictAcceptClient(Entity serverEntity, Entity clientEntity) => ConflictManager.AcceptClient;
ConflictManager handleConflictAcceptServer(Entity serverEntity, Entity clientEntity) => ConflictManager.AcceptServer;

void main() {
  ormInitialize();
  
  Map<String, dynamic> person = <String, dynamic>{};
  List<Map<String, dynamic>> properties = <Map<String, dynamic>>[];
  
  Map<String, dynamic> p_id = <String, dynamic>{};
  Map<String, dynamic> p_name = <String, dynamic>{};
  Map<String, dynamic> p_group = <String, dynamic>{};
  
  p_id['name'] = 'id';
  p_id['column'] = 'Person_id';
  p_id['type'] = 'int';
  p_id['isIdentifier'] = true;
  p_id['insertValue'] = 0;
  
  p_name['name'] = 'name';
  p_name['column'] = 'Name';
  p_name['type'] = 'String';
  p_name['defaultValue'] = '';
  
  p_group['name'] = 'group';
  p_group['column'] = 'PersonGroup_id';
  p_group['type'] = 'PersonGroup';
  p_group['defaultValue'] = null;
  
  properties.add(p_id);
  properties.add(p_name);
  properties.add(p_group);
  
  person['name'] = 'Person';
  person['table'] = 'Persons';
  person['extends'] = 'MutableEntity';
  person['properties'] = properties;
  
  print(JSON.encode(person));
  
  /*serializer.addRule(
    String,
    (String value) => 'testIn',
    (String value) => 'testOut'
  );*/
  
  init();
  //benchmark();
}

void benchmark() {
  fetchService = new FetchService(url, port, serializer, handleConflictAcceptClient);
  
  fetchService.ormEntityLoad('Employee').then(
      (ObservableList<Entity> resultList) {
        window.animationFrame.whenComplete(benchmark);
      }
  );
}

void init() {
  ObservableList resultList = new ObservableList();
  int x = 0;
  
  for (x=0; x<1000; x++)
  
  resultList.add(
      new Employee()
      ..name = '$x'
      ..id = x
  );
  
  DartFlexRootContainer rootContainer = new DartFlexRootContainer(elementId: '#dynamic_content')
  ..layout=new VerticalLayout();
  
  HGroup header = new HGroup()
  ..percentWidth=100.0
  ..height=30;
  
  DataGrid grid = new DataGrid()
  ..percentWidth=100.0
  ..percentHeight=100.0
  ..headerHeight=40
  ..rowHeight=80
  ..columnSpacing=0
  ..rowSpacing=0
  ..columns = new ObservableList.from(
      [
       new DataGridColumn()
       ..width = 80
       ..headerData = const HeaderData('', Person.ID_SYMBOL, 'id', 'person id')
       ..field = Person.ID_SYMBOL
       ..headerItemRendererFactory = new ItemRendererFactory(constructorMethod: HeaderItemRenderer.construct)
       ..columnItemRendererFactory = new ItemRendererFactory(constructorMethod: LabelItemRenderer.construct),

       new DataGridColumn()
       ..width=50
       ..headerData = const HeaderData('', Person.NAME_SYMBOL, 'name', 'person name')
       ..field = Person.NAME_SYMBOL
       ..headerItemRendererFactory = new ItemRendererFactory(constructorMethod: HeaderItemRenderer.construct)
       ..columnItemRendererFactory = new ItemRendererFactory(constructorMethod: EditableLabelItemRenderer.construct),

       new DataGridColumn()
       ..width=50
       ..headerData = const HeaderData('', Person.NAME_SYMBOL, 'name', 'person name')
       ..field = Person.NAME_SYMBOL
       ..headerItemRendererFactory = new ItemRendererFactory(constructorMethod: HeaderItemRenderer.construct)
       ..columnItemRendererFactory = new ItemRendererFactory(constructorMethod: EditableLabelItemRenderer.construct),
       
       new DataGridColumn()
       ..width=50
       ..headerData = const HeaderData('', Person.NAME_SYMBOL, 'name', 'person name')
       ..field = Person.NAME_SYMBOL
       ..headerItemRendererFactory = new ItemRendererFactory(constructorMethod: HeaderItemRenderer.construct)
       ..columnItemRendererFactory = new ItemRendererFactory(constructorMethod: EditableLabelItemRenderer.construct),
       
       new DataGridColumn()
       ..width=50
       ..headerData = const HeaderData('', Person.NAME_SYMBOL, 'name', 'person name')
       ..field = Person.NAME_SYMBOL
       ..headerItemRendererFactory = new ItemRendererFactory(constructorMethod: HeaderItemRenderer.construct)
       ..columnItemRendererFactory = new ItemRendererFactory(constructorMethod: EditableLabelItemRenderer.construct),
       
       new DataGridColumn()
       ..width=50
       ..headerData = const HeaderData('', Person.NAME_SYMBOL, 'name', 'person name')
       ..field = Person.NAME_SYMBOL
       ..headerItemRendererFactory = new ItemRendererFactory(constructorMethod: HeaderItemRenderer.construct)
       ..columnItemRendererFactory = new ItemRendererFactory(constructorMethod: EditableLabelItemRenderer.construct),
       
       new DataGridColumn()
       ..width=50
       ..headerData = const HeaderData('', Person.NAME_SYMBOL, 'name', 'person name')
       ..field = Person.NAME_SYMBOL
       ..headerItemRendererFactory = new ItemRendererFactory(constructorMethod: HeaderItemRenderer.construct)
       ..columnItemRendererFactory = new ItemRendererFactory(constructorMethod: EditableLabelItemRenderer.construct),
       
       new DataGridColumn()
       ..width=50
       ..headerData = const HeaderData('', Person.NAME_SYMBOL, 'name', 'person name')
       ..field = Person.NAME_SYMBOL
       ..headerItemRendererFactory = new ItemRendererFactory(constructorMethod: HeaderItemRenderer.construct)
       ..columnItemRendererFactory = new ItemRendererFactory(constructorMethod: EditableLabelItemRenderer.construct),
       
       new DataGridColumn()
       ..width=50
       ..headerData = const HeaderData('', Person.NAME_SYMBOL, 'name', 'person name')
       ..field = Person.NAME_SYMBOL
       ..headerItemRendererFactory = new ItemRendererFactory(constructorMethod: HeaderItemRenderer.construct)
       ..columnItemRendererFactory = new ItemRendererFactory(constructorMethod: EditableLabelItemRenderer.construct),
       
       new DataGridColumn()
       ..width=50
       ..headerData = const HeaderData('', Person.NAME_SYMBOL, 'name', 'person name')
       ..field = Person.NAME_SYMBOL
       ..headerItemRendererFactory = new ItemRendererFactory(constructorMethod: HeaderItemRenderer.construct)
       ..columnItemRendererFactory = new ItemRendererFactory(constructorMethod: EditableLabelItemRenderer.construct),
       
       new DataGridColumn()
       ..width=50
       ..headerData = const HeaderData('', Person.NAME_SYMBOL, 'name', 'person name')
       ..field = Person.NAME_SYMBOL
       ..headerItemRendererFactory = new ItemRendererFactory(constructorMethod: HeaderItemRenderer.construct)
       ..columnItemRendererFactory = new ItemRendererFactory(constructorMethod: EditableLabelItemRenderer.construct),
       
       new DataGridColumn()
       ..width=50
       ..headerData = const HeaderData('', Person.NAME_SYMBOL, 'name', 'person name')
       ..field = Person.NAME_SYMBOL
       ..headerItemRendererFactory = new ItemRendererFactory(constructorMethod: HeaderItemRenderer.construct)
       ..columnItemRendererFactory = new ItemRendererFactory(constructorMethod: EditableLabelItemRenderer.construct),
       
       new DataGridColumn()
       ..width=50
       ..headerData = const HeaderData('', Person.NAME_SYMBOL, 'name', 'person name')
       ..field = Person.NAME_SYMBOL
       ..headerItemRendererFactory = new ItemRendererFactory(constructorMethod: HeaderItemRenderer.construct)
       ..columnItemRendererFactory = new ItemRendererFactory(constructorMethod: EditableLabelItemRenderer.construct),
       
       new DataGridColumn()
       ..width=50
       ..headerData = const HeaderData('', Person.NAME_SYMBOL, 'name', 'person name')
       ..field = Person.NAME_SYMBOL
       ..headerItemRendererFactory = new ItemRendererFactory(constructorMethod: HeaderItemRenderer.construct)
       ..columnItemRendererFactory = new ItemRendererFactory(constructorMethod: EditableLabelItemRenderer.construct),
       
       new DataGridColumn()
       ..width=50
       ..headerData = const HeaderData('', Person.NAME_SYMBOL, 'name', 'person name')
       ..field = Person.NAME_SYMBOL
       ..headerItemRendererFactory = new ItemRendererFactory(constructorMethod: HeaderItemRenderer.construct)
       ..columnItemRendererFactory = new ItemRendererFactory(constructorMethod: EditableLabelItemRenderer.construct),
       
       new DataGridColumn()
       ..width=50
       ..headerData = const HeaderData('', Person.NAME_SYMBOL, 'name', 'person name')
       ..field = Person.NAME_SYMBOL
       ..headerItemRendererFactory = new ItemRendererFactory(constructorMethod: HeaderItemRenderer.construct)
       ..columnItemRendererFactory = new ItemRendererFactory(constructorMethod: EditableLabelItemRenderer.construct)
       ]
  )
  ..dataProvider = resultList;
  
  rootContainer.addComponent(header);
  rootContainer.addComponent(grid);
}
  /*
  // loading data can be done through 2 library-level methods
  
  // EXAMPLE 1: you can load an entity by primary key...
  ormEntityLoadByPK('Job', 1, onConflict:handleConflictAcceptClient).then(
    (Job job) => print('A Job is loaded! The job is called: ${job.name}')
  );
  
  // EXAMPLE 2: or load a list of entities...
  ormEntityLoad('Job', onConflict:handleConflictAcceptClient).then(
      (List<Entity> resultList) {
        print('All jobs are now loaded!');
        resultList.forEach(
            (Job job) => print('-> The job is called: ${job.name}')
        );
      }
  );
  
  // EXAMPLE 3: you can also add a 'where' clause to the list loader...
  // the where clause must be a Map...
  Map<String, dynamic> whereMap1 = new Map<String, dynamic>();
  
  // in this map, we add the fields to filter upon...
  whereMap1[Job.NAME] = 'Managing director';
  // add extra fields where needed
  
  ormEntityLoad('Job', where:whereMap1, onConflict:handleConflictAcceptClient).then(
      (List<Entity> resultList) {
        print('A filtered job list is now loaded');
        resultList.forEach(
            (Job job) => print('-> The job is called: ${job.name}')
        );
      }
  );
  
  // entities can have other entities as properties, or lists of entities
  // let's load an employee. An employee has a job property, and this job property in its turn, has an employees list, 
  // this list contains all employees that have this job
  
  Map<String, dynamic> whereMap2 = new Map<String, dynamic>();
  
  // Note that 'Employee' extends 'Person',
  // Employee adds one extra property, job
  // the other properties are in the parenting class Person
  // So for the where clause by name, we target Person.NAME
  
  whereMap2[Person.NAME] = 'Jane Austin';
  
  ormEntityLoad('Employee', where:whereMap2, onConflict:handleConflictAcceptClient).then(
      (List<Entity> resultList) {
        resultList.forEach(
            (Employee employee) {
              print('Employee ${employee.name} is now loaded');
              
              employee.job.employees.forEach(
                (Employee employeeWhoHasSameJob) => print('This employee is also a ${employee.job.name}: ${employeeWhoHasSameJob.name}')    
              );
            }
        );
      }
  );
  
  // Conflicts can occur when a client-side entity is out of sync with an incoming server-side entity
  // for example, you load a job, change the job name and then decide to reload the same job from the server
  // at this point, they are out of sync, because the client has an uncommitted update.
  // to handle such a conflict, you can set the onConflict argument,
  // the value should be a Function, which will be run in case of a detected conflict
  
  // we start by loading a specific job :
  ormEntityLoadByPK('Job', 1, onConflict:handleConflictAcceptServer).then(
    (Job job) {
      // then change the name
      job.name = 'a new job name here';
      
      // finally, reload the same job, and choose to accept the server version
      ormEntityLoadByPK('Job', 1, onConflict:handleConflictAcceptServer).then(
          (Job jobReloaded) => print(jobReloaded.name)
      );
    }
  );
  
  ormEntityLoadByPK('Job', 1, onConflict:handleConflictAcceptClient).then(
      (Job job) {
        Map<String, dynamic> whereClause = new Map<String, dynamic>();
        
        whereClause[Employee.JOB] = job;
        whereClause[Person.NAME] = 'Martin Svensson';
        
        ormEntityLoad('Employee', onConflict:handleConflictAcceptClient, where:whereClause).then(
            (List<Entity> resultList) {
              resultList.forEach(
                  (Employee employee) => print('${employee.name} ${employee.job.name}')
              );
            }
        );
        
        job.name = 'Chief Executable Officer';
        
        dormManager.queue(job);
        
        commitService.flush(dormManager).then(
            (_) => print('flushed!')
        );
      }
  );
  
  
  ormEntityLoad('Job', onConflict:handleConflictAcceptClient).then(
      (List<Entity> resultList) {
        resultList.forEach(
            (Job job) => print('${job.name} has ${(job.employees != null) ? job.employees.length : 0} registered employees')
        );
      }
  );
  
  ormEntityLoadByPK('User', 1, onConflict:handleConflictAcceptClient).then(
      (User user) => print('${user.id} ${user.name}')
  );*/

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  dorm: "^1.0.42"

2. Install it

You can install packages from the command line:

with pub:


$ pub get

with Flutter:


$ flutter packages get

Alternatively, your editor might support pub get or packages get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:dorm/dorm.dart';
        
Version Uploaded Documentation Archive
1.0.42 Feb 19, 2018 Go to the documentation of dorm 1.0.42 Download dorm 1.0.42 archive
1.0.41 Apr 5, 2017 Go to the documentation of dorm 1.0.41 Download dorm 1.0.41 archive
1.0.40 Jan 17, 2017 Go to the documentation of dorm 1.0.40 Download dorm 1.0.40 archive
1.0.39+1 Jan 12, 2017 Go to the documentation of dorm 1.0.39+1 Download dorm 1.0.39+1 archive
1.0.39 Jan 12, 2017 Go to the documentation of dorm 1.0.39 Download dorm 1.0.39 archive
1.0.37 Nov 30, 2016 Go to the documentation of dorm 1.0.37 Download dorm 1.0.37 archive
1.0.35 Nov 25, 2016 Go to the documentation of dorm 1.0.35 Download dorm 1.0.35 archive
1.0.34 Sep 29, 2016 Go to the documentation of dorm 1.0.34 Download dorm 1.0.34 archive
1.0.33 Sep 29, 2016 Go to the documentation of dorm 1.0.33 Download dorm 1.0.33 archive
1.0.32 Sep 2, 2016 Go to the documentation of dorm 1.0.32 Download dorm 1.0.32 archive

All 87 versions...

Analysis

We analyzed this package on Apr 23, 2018, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.0.0-dev.49.0
  • pana: 0.10.6

Scores

Popularity:
Describes how popular the package is relative to other packages. [more]
67 / 100
Health:
Code health derived from static analysis. [more]
78 / 100
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
55 / 100
Overall score:
Weighted score of the above. [more]
68
Learn more about scoring.

Platforms

Detected platforms: Flutter, web, other

No platform restriction found in primary library package:dorm/dorm.dart.

Suggestions

  • Maintain CHANGELOG.md.

    Changelog entries help clients to follow the progress in your code.

  • Fix analysis and formatting issues.

    Analysis or formatting checks reported 50 errors 27 hints.

    Strong-mode analysis of lib/src/codegen/codegen.dart failed with the following error:

    line: 144 col: 23
    Undefined class 'PropertyAccessorElement'.

    Strong-mode analysis of lib/src/codegen/utils.dart failed with the following error:

    line: 1 col: 8
    Target of URI doesn't exist: 'package:analyzer/dart/element/element.dart'.

    Similar analysis of the following files failed:

    • lib/src/serialization/serializer_json.dart (error)
    • lib/dorm.dart (hint)
    • lib/dorm_test.dart (hint)
    • lib/src/core/dorm_error.dart (hint)
    • lib/src/core/dorm_proxy.dart (hint)
    • lib/src/core/entity_assembler.dart (hint)
    • lib/src/core/entity_factory.dart (hint)
    • lib/src/core/entity_scan.dart (hint)
    • lib/src/core/metadata_cache.dart (hint)
    • lib/src/core/metadata_validation_result.dart (hint)
    • lib/src/core/property_data.dart (hint)
    • lib/src/domain/entity.dart (hint)
    • lib/src/domain/meta.dart (hint)
    • lib/src/serialization/entity_codec.dart (hint)
    • lib/src/serialization/externalizable.dart (hint)
    • lib/src/serialization/serialization_type.dart (hint)
    • lib/src/serialization/serializer.dart (hint)
    • lib/src/server/build_entities.dart (hint)
    • lib/src/test/another_test_entity.dart (hint)
    • lib/src/test/test_entity.dart (hint)
    • lib/src/test/test_entity_super.dart (hint)
  • Use analysis_options.yaml.

    Rename old .analysis_options file to analysis_options.yaml.

Dependencies

Package Constraint Resolved Available
Dev dependencies
benchmark_harness any
build 0.9.3
build_runner 0.3.4+1
source_gen >=0.5.0 <0.6.0
test any