Skip to content

Instantly share code, notes, and snippets.

@saem
Created July 25, 2016 02:02
Show Gist options
  • Save saem/b005943b2b001c91f750af12687d7e25 to your computer and use it in GitHub Desktop.
Save saem/b005943b2b001c91f750af12687d7e25 to your computer and use it in GitHub Desktop.
Initial thoughts on how to merge my thinking around Aggregates (named transaction boundaries), Entity-Component-Systems, and Relational Databases. This will evolve over time, it's currently incomplete.
public final class Thoughts { private Thoughts() {} }
/**
* # Introduction
*
* A number of thoughts/concerns floating in my head:
* 1 - DDD Aggregates, specifically transaction boundaries [1]
* 2 - Entity-Component-Systems, how games are data oriented, high performance
* databases [2][3]
* 3 - How 1 & 2 work with relational databases
* 4 - How 1 & 2 can be unified, such that we can express actions happening
* within aggregates, of differing/similar types, either in memory, or
* expressed as a query and run on the remote data store (this latter piece
* is an rare optimization, but absolutely necessary)
* 5 - Out of the Tar Pit's suggestions around program/infrastructure, and
* creating a higher level relational algebra that is converted into actual
* queries to be executed (I specifically have reservations about exposing
* relational algebra so broadly, hopefully made clear later) [4]
* 6 - Declarative side-effects in that we can push the them down/out/to the
* edge of a design (mostly because this makes testing easier). Here Object
* Algebras come to mind
* 7 - How to smash these ideas together? I'm currently thinking if I express
* these ideas within some simple interfaces/generics, things will become
* clearer for myself, and others.
*
* This is an attempt to reconcile a number of concepts so it's a whole lot
* easier to implement ReST services, and not end up in some MVC spaghetti that
* most people end up writing. Said spaghetti seems unavoidable, regardless of
* whether you're writing functional, or object oriented code.
*
* ## Issues
* 1 - I'm not really thinking too much about concurrency, across aggregates,
* within aggregates, nor across ReST calls. The first might be not too
* terrible with Java parallel streams. The second, might be workable with
* Futures/Promises. While the last one will require much more
* consideration (Disruptor[5], and Orbit[6] come to mind).
*
* ## References
*
* [1] http://martinfowler.com/bliki/DDD_Aggregate.html
* [2] https://github.com/libgdx/ashley/wiki/How-to-use-Ashley
* [3] http://www.dataorienteddesign.com/dodmain/dodmain.html
* [4] https://blog.acolyer.org/2015/03/20/out-of-the-tar-pit/
* [5] http://martinfowler.com/articles/lmax.html
* [6] https://github.com/orbit/orbit/wiki
*/
/**
* # Tour of Sketches/Concepts
*
* The next few sections are concepts that have been mentioned above, and some
* sketches of code around them to give people some ideas, if it seems really
* spare, then that likely means I need to give it more thought.
*/
/**
* Inspired by https://github.com/libgdx/ashley/wiki/Framework-overview
*
* Here Family's are used to filter/project out information we want, and also
* gives us a sort of record polymorphism. So we could have User, People, and
* Company type entities in our system, all with email addresses, and we could
* conceivably have a 'Emailable' family, which can contain a mixture of these
* which could then be emailed.
*
* The major downside here is that this very much designed around in memory
* storage, or at best a single consistent data store. For example, selecting
* a single entity by ID, or username, or what have you. You'd either have to
* create a component for every single individual item you wanted to filter
* against.
*
* Instead if Families had a concept of declaratively filtering for within
* component data, we can have the lower "infrastructure" layer, that is SQL
* read those filters and cut down the data, while still using the component
* information for determining what's being projected, and/or filtered upon.
* This is more than an optimization.
*/
interface Component {}
interface Entity {}
interface Filter {}
interface Family {
FilteredFamily filter(final Filter filter);
}
interface UnfilteredFamily extends Family {}
interface FilteredFamily extends Family {}
interface System<F extends Family, R, P extends Function<? extends Entity, R>> {
R process(P processor);
}
/**
* A Family algebra might look like this, where we take in components, as
* broader items to project, and filter on.
*
* @param <F> The family being built
* @param <C> The component types we're accepting
* @param <P> The predicate description that should be used to filter the family
*/
interface FamilyAlgebra<F, C, P> {
F all(final C... component);
F anyOf(final C... component);
F noneOf(final C... component);
F filter(final P filter);
}
/**
* A first sketch of an explicit aggregates system
*
* Here aggregates have a concept of transactions, which take themselves, and
* then return a possibly transformed value. This let's us have Transactions
* functions that compose in logic, database side-effects, tracing, etc... while
* having the database still provide the physical transaction boundaries, and
* the domain honour the logical ones (the physical, must allow for the logical)
*/
interface Transaction<A extends Aggregate<A>> extends Function<A, A> {}
interface Aggregate<A extends Aggregate<A>> {
default A transaction(final Transaction<A> transaction) {
// an unchecked exception is required because we can't express the fact
// that this the value, is the exact same type as the A parameter within
// the type, more information is here:
// http://www.angelikalanger.com/GenericsFAQ/FAQSections/ProgrammingIdioms.html#FAQ206
return transaction.apply((A) this);
}
}
interface AggregateRoot<A extends AggregateRoot<A>> extends Aggregate<A> {}
/**
* So for funsies, we can create an algebra, instead of having interfaces, where
* Aggregates can have transactions run, but they're composed on more record
* like structures, rather than building it into interfaces. This should be more
* Open-Closed (https://en.wikipedia.org/wiki/Open/closed_principle)
*/
interface AggregateAlgebra<A> {
A aggregate(final Aggregate aggregate);
A transact(final Function<A, A> transaction);
}
/**
* Attempt to smush it all together (not done)
*/
@saem
Copy link
Author

saem commented Jul 25, 2016

So in the section talking about Entity-Component-Systems (ECS), I'd like to find a way to make the Family's type safe. Maybe introduce a concept of FamilyMember, which is a record consisting of all the referenced Components. I have no idea, beyond Optional, as to how I'm going to deal with the anyOf Components. But a type safe pattern to compose a Family, and consequently a FamilyMember still allude me.

After that it's a matter of fleshing out more of the ECS algebra such that I can see how ECS works within the confines of Aggregates. Effectively, I need to do transaction demarcation, ideally in some sort of wonderful implicit/obvious way, here it maybe that aggregates themselves take care of this for me by definition, as they'll fail to match Families that get greedy with components. Of course, detecting broken queries because of aggregate boundaries changing after the fact is still scary.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment