Since last week, I was able to define a schema for a graph database. I also did my botany refreshment, defined my nodes and relationships. Implementation-wise, I have a recipe for building the form dynamically, based on JSON data generated from the schema, in Vue.js with Quasar Framework. If my transformer in the backend was made right, this should allow me to change the schema in the future, while still generating the frontend bits that depend on it. I've setup a stack, with what I would call the VEGAN stack (lol, pun totally intended) - Vue, Express, GraphQL, Apollo, Neo4J. Or VEGANN (if you insist on specifying Node).
The front end is tested with only one of the types, so far, but it should work just the same for the rest. Both back- and frontend use websockets, and the backend pushes the JSON to the client when connected (which happens, if all goes well, when the Vue app is mounted).
Saving data in the database is not yet achieved, because I have to iron out a few things like: since I don't want the frontend to change each time the backend or schema change, should I include GraphQL in the frontend? My initial thought was "yes" because it would be easy to get stuff rolling. But, on second thought, a request to the websocket for specific information would give me the data I want. Surely my schema design would not put me at risk of over-fetching! (Of course it will, but then I operate on the queries in the backend - no need to hard couple the front-end to the schema or queries - just build the UI with data!). Ambitious, I know. But interesting challenge, don't you think?
On to the nitty-gritty.
Types & relations
- Source - where I found information or bought the seeds (or bulbs, or plants, etc). This would usually be a website, with a name, and the date it was used/accessed.
- CatalogEntry - is the main type. This represents a species, subspecies, variety, or cultivar of which I own or plan to own a seed, bulb, etc. It is not the same as the seed, bulb or corm. This is "top level" information about the plant. It should contain sufficient information on it so as not to require even "higher level" stuff (taxonomical information - because that's for v2.0 if ever). Some of the properties describe whether it's perennial or annual, how wide and tall it can grow, what distance it should have from other plants and how often it should be rotated, as well as names, images. It has relationships to Sources, and other items of the type CatalogEntry - as either companion or antagonist. It also has relationships to Propagators (as for a single variety I might purchase seeds multiple times, from different places, or sometime I would buy a seed, another time a potted plant). Notes and observations for future record will also help.
- Propagator - the seed, bulb, corm, bare root plant, etc. This is the physical means of propagation, purchased from a source. Its properties describe details of what minimum temperature it can tolerate, how long until it germinates, if it has to be started early, resow at what interval (for short lived cultures such as lettuce or radishes), sowing depth, notes & observations, and a decision - buy again or not.
- Culture - represents the time and place where a Propagator is in the ground, taking up the space. It has a start and end date (optional), number of plants, whether it's been harvested or not, and when, as well as any observations (what went well or wrong, recommendations for next time). Cultures will have relationships to places in the garden (as I use raised beds this will be easy) and propagators (which have a relation to the Catalog Entry, telling me what this thing actually is - i.e. Leek "Giant Winter").
- PlantingArea - the raised beds, of circular or polygonal shape, of specific dimensions (circumference, diameter, radius, or number of sides and their length, respectively, and in both cases height), a location (a Point with x, y, z coordinates) and relationshing to Cultures.
Pheew. Now, of course, in order to keep the structure flat and friendly, and the data consistent, in addition to property types such as String, Int, Float, Boolean and Point, I had to resort to enums. I have defined enums for pretty much any unit of measure (time, space, temperature), source types, planting area shapes, growth habbit, lifespan.
Some of the types share properties - such as notes and observations, source(s). I had to carefully consider where it made sense to use interfaces or not. I decided it made sense to use an interface for PlantingArea(interface) - which has two shape-dependent implementations - PlantingAreaCircle and PlantingAreaPolygon - we don't care to see a circumference input field on a square, do we?
On the other hand, CatalogEntry and Propagator, have some common properties, but defining an interface would be overkill. Plus, it's not like I would have a common label for the two.
What helped in making these choices was the question: Would I ever want to see the listing of BOTH CatalogEntry and Propagator at the same time? No. How about PlantingAreaCircle and PlantingAreaPolygon? Yes. From a technical point of view, I would label these as
PlantingArea in Neo4J.
See this gist
Minimum Viable Product
As I wrote before, the scope of the application was corrupted by my desire to build something mindblowing that gardeners and botanists alike could use. While I don't dismiss that idea entirely, I do prefer to work by agile methodologies so I had to decide what a minimum viable product (MVP) would look like. While designing it, I should keep in mind further development and new features, in as much as possible.
So how would an MVP look like? Since I build this for myself and I, I have decided that, MVP v1.0 should deliver:
- An entry form that will allow me to create all kinds of things on the fly and sync my state ASAP. So, if I have a new packet of seeds, I should be able to put in the data for a CatalogEntry, the Propagator (my seed packet) and connect the two, add the sources and connect them to their respective node. I should also be able to create Cultures and PlantingAreas on the spot, or choose from existing ones.
- While point 1. is achieved, I should refine and revise the schema, in preparation for the "drawing board". This would be a canvas where I render all my planting areas, with a symbolic representation of the Cultures associated with them. I should be able then to clone a culture (have two small blocks of spinach in a raised bed), drag, rotate and resize it, so that it would finally calculate the number of plants for the culture, ideally based on a planting pattern / technique (triangular or other). For any given CatalogEntry, represented by a propagator, I should be able to choose from a list of companions, and if a Propagator is available, add a companion Culture in full color, else a greyed out one.
At the end, I should have:
- a list of all the CatalogEntries to be planted with "need to buy" note and the number of plants/propagators I need;
- a layout plan to follow - where to plant what, and together with which others.
- Learn more Clojure(Script) - already well on the way with some fantastic courses by Jacek Schae, which promise to help me master ClojureScript front-end development with Reagent and re-frame. Btw, the above is an affiliate link and I will get 10% of your muneyz. A girl's gotta buy seeds and fertilizer, yo!
- Have another look at Lacinia, a GraphQL implementation for Clojure, as Neo4J can be queried with GraphQl.
- If I still find it hard to wrap my head around working with GraphQL via Clojure, why not just Clojure and Cypher. Either way, I have 8-9 new technologies and tools to master. This is going to be lots of fun!
This week's lessons
- Tackling Clojure(Script) again, I realized all the reasons to love it and be intimidated by it at the same time. While last time, I allowed myself to be intimidated, this time I go with love.
- Carry smaller loads! I have a tendency to overthink things. And perhaps a bit of inclination for the waterfall method. These combined would lead to me never, ever getting to build this product and using it.
- Accountability really works! Usually, my pet projects like this one end up in the pile private repos (read half-baked, quasi-efforts) because work or life take over. Even if my accountability is to myself and the 1-2 readers accidentally ending up here, it's done wonders so far. (I could have been spending my time netflixing instead, right?)
- Refreshed the little knowledge I had of Clojure and tooling around/for it, some Reagent, some more GraphQL, Cypher and Neo4J. And learned a ton more. The more I learn, the more I like.
- The freelance world hasn't changed much since 2015 - it just got bigger, but better? Don't know about that.