Fork me on GitHub
#datomic
<
2018-06-07
>
johnj03:06:56

are all features of ion available for solo?

steveb8n04:06:46

has anyone tested AWS App-Sync using an Ion Lambda? i.e. graphql api for Ion without any code

chrisblom09:06:42

ehm, wait no

chrisblom09:06:59

if its like ring, :protocol should be something like “The protocol the request was made with, e.g. “HTTP/1.1".” and :scheme is :http or :https

stuarthalloway10:06:41

@chris.blom thanks! You are right, :protocol should be like Ring

chrisblom12:06:32

Ok, it was not immediately clear to me what ion is about: My understanding now is that: - its an application server integrated with datomic - has build in tooling based on deps.edn to deploy based on git revisions - it integrates with AWS Lambda and API Gateway to handle http requests, in a mostly ring compatible way Its not clear to me what “deploying your code to a running Datomic cluster” entails: - What exactly runs on Lambda, and what runs on the datomic cluster? - What are the limitations of running code in a datomic cluster? Can I access the local disk and other AWS services? - How does the autoscaling work? - Is it possible to develop and test ions locally? - Can I run some sort of test environment for CI testing?

Alex Miller (Clojure team)12:06:10

Essentially all of your code is running on the d cluster. Being there you can access all the aws services. For storage, I think you’d use aws storage services, not disk. Stu or Rich can probably answer some of the others better than I can but generally the answers will be to use the aws functionality for autoscaling, ci, etc.

chrisblom12:06:56

ok, so the Lambda functions for an Ion are just glue to interface with the outside world, and delegate the actual work to the Datomic cluster?

chrisblom12:06:17

thanks, good to know

stuarthalloway12:06:38

I care a lot about local dev (Give me REPL or give me death!)

🙏 28
stuarthalloway12:06:37

Client API now supports :server-type :ion, which connects remotely when you dev on your laptop, but connects in memory when you deploy the same code to Datomic: https://docs-gateway-dev2-952644531.us-east-1.elb.amazonaws.com:8181/cloud/ions/ions-reference.html#server-type-ion

andrewhr12:06:15

stu, the transaction producing functions will run on whatever node in datomic cluster, right? But the final transactions are still directed to the node acting as transactor? (so essentially, it’s like the peer model)

richhickey12:06:10

@andrewhr the tx fns run where the txes do. There isn't a dedicated transactor per se as with on prem

richhickey12:06:16

but you will be able to have independent clusters running app/query code and handling txes

andrewhr13:06:32

I remember something about “avoiding contention”, but in retrospect doesn’t make too much sense giving ddb could probably just autoscale in response. Maybe this image make me a little confused https://docs.datomic.com/cloud/whatis/architecture.html#production-topology

andrewhr13:06:37

as far as I understand (together with you previous explanation), query groups aka “extra clusters” will tunnel their transactions thought the primary tx group

andrewhr13:06:50

or when do you say “extra clusters” you’re really meaning “one set of storage resources” + “multiple sets of primary compute resources”?

Chris Bidler13:06:40

@steveb8n I gave a talk last month at Serverless Chicago about using Datomic Cloud with AppSync, you may expect some kind of preliminary blog post or code sample extending that talk to Ions like …today?

👍 8
Chris Bidler13:06:55

I am finding it very difficult to focus on my day-job work right now, knowing that I could be spinning up a Cloud instance in my personal account and exploring getting AppSync to run, looking at modeling what $day-job does with the txn report queue in Ions callbacks, etc. etc. so …I don’t think it will be too long before I have a trip report re: AppSync ready for people to read hehe

8
steveb8n05:06:47

Agreed, lots of people will be interested to know the graphql options using API Gateway on top of Ions.

steveb8n05:06:09

fallback would be Lacinia but App Sync would be better

steveb8n05:06:35

best would be App Sync subscriptions support. Somehow I doubt that’s possible. What do you think?

richhickey13:06:45

@chris.blom

- What exactly runs on Lambda, 
a generic proxy. We call it 'Ultimate, the lambda'

- and what runs on the datomic cluster? 
everything

- What are the limitations of running code in a datomic cluster? Can I access the local disk and other AWS services?
AWS services sure. It is *your* instance, running in *your* VPC. That said, local disk, probably not a great idea. 

- How does the autoscaling work?
You can trigger autoscaling of the cluster on any of various metrics we or AWS produce.

- Is it possible to develop and test ions locally?
As Stu said, sure! The db API you'll see in the ion is the same as the client sync API, and the :ion server type dynamically loads the right back end.

- Can I run some sort of test environment for CI testing?
Yes. You can run a solo instance that is a target of the same application, deploying early revs to it and tested revs to prod.

chrisblom13:06:49

thanks, that clears things up

chrisblom13:06:28

also, its the answers i was hoping for 😁

eggsyntax13:06:47

"We call it 'Ultimate, the lambda'" That's awesomely horrible facepalm 😂

😂 12
dominicm13:06:12

I suppose if everything runs in the cluster, then there's no way to restrict certain functions to certain operations, as you can do with Lambdas?

jeroenvandijk13:06:05

We had some bad experiences with AWS lambda in the past: 1. It has a global queue per account (our solution: never use AWS lambda for anything of high throughput as it will block other tasks unexpectedly). 2. AWS requires node updates sometimes (one time within 3 months from launch). @richhickey Are these issues taken into account? Is the ultimate lambda free of these concerns? Thank you.

richhickey13:06:29

@dominicm There are distinct instances of ultimate the lambda and each is an independent AWS Lambda and proxies to a particular fn on a particular Datomic compute group. From there you have all the ordinary wiring up of Lambdas available, to particular events etc.

richhickey14:06:22

@jeroenvandijk AWS has improved #1 with per-Lambda concurrency reservations/limits (which we expose as a knob). Not sure I understand 2 - lambda's internal nodes?

jeroenvandijk14:06:14

@richhickey Thank you, interesting. Regarding 2 sorry i meant the node.js runtime (assuming you use this, but might apply to jvm runtime too). We were forced to upgrade the node.js runtime and didn't have a choice to leave it as is (like how you can choose to stick with an old AMI version)

richhickey14:06:21

@jeroenvandijk we care very little about the lambda runtime as we're not doing much there, and your ion code does not run there so cares not at all

richhickey14:06:17

I guess you might occasionally have to roll for things like that

jeroenvandijk14:06:56

Ok understood. Thank you. I guess it could have been more like a one time occurrence.

jeroenvandijk14:06:58

One other thing I noticed in the architecture of Datomic Cloud (https://docs.datomic.com/cloud/whatis/architecture.html) is that the storage of record is S3 and Dynamodb is used as transaction log. Is it possible to clean up the transaction log every now and then and rely on S3 for older data in order to save data costs? (With a self-hosted Datomic setup we have a big dynamodb table and this would be a potential cost saver)

johnj14:06:06

Can ions be used for light/hobbie stuff with solo?

stuarthalloway14:06:46

@lockdown- yes, or even medium/moonlighting stuff 🙂

👍 8
stuarthalloway14:06:52

@jeroenvandijk yes, getting the log into S3 is an optimization we intend to implement

stuarthalloway14:06:50

@jeroenvandijk for many use cases, Cloud is already cheaper than On-Prem with DDB just because indexing doesn’t have to hit DDB

jeroenvandijk14:06:35

@stuarthalloway Nice 🙂 TBH we have been really abusing Datomic for things that is advised against. We have also reached the 10 billion datom limit times 4.. Is this something that Cloud is also addressing?

johnj14:06:51

is says to omit the first argument (the database) but the example is including it?

Dustin Getz15:06:21

In practice how are classpath functions most commonly invoked? [:find (pull ?f [:db/id *]) :where [(partial contrib.datomic/datomic-entity-successors $) ?succ] [(loom.alg-generic/bf-traverse ?succ :db/ident) [?f ...]]] vs (->> (loom.alg-generic/bf-traverse (partial contrib.datomic/datomic-entity-successors $) :db/ident) (d/pull-many $ [:db/id *])) The first has constraints on the complexity of the clojure form, but is more structured

devn15:06:42

@jeroenvandijk out of curiosity, how are you abusing it?

jeroenvandijk15:06:55

@U06DQC6MA Biggest abuse is that we are using it as a timeseries database basically. This is causing (too) many transactions and a huge dynamodb size. Another less serious abuse it that we are storing relative big string blobs in Datomic

octahedrion15:06:31

is there a way I can pull attributes that are not :db/valueType :db.type/ref ?

octahedrion15:06:08

without knowing what their names are in advance ?

Dustin Getz15:06:21

Can you query schema and use that to decide the pull

octahedrion15:06:13

that's what I'm trying to do, but I can't see how to use the variable from the :where to inform the pull

Dustin Getz15:06:29

you need a second query

4
octahedrion15:06:52

i did not think of that

Dustin Getz15:06:14

You can also go wild with subqueries in this fashion http://www.hyperfiddle.net/:cookbook.recipe!datomic-subquery/

4
octahedrion15:06:16

also, is there a way in the pull to use a wildcard with a recursion limit ?

octahedrion15:06:39

I tried {* 2} but doesn't return anything

Dustin Getz15:06:52

i dont know the answer to that sorry

kenny15:06:34

Also, is there a story for web apps that have real-time requirements (i.e. Web sockets or SSE)? I don't believe AWS API Gateway has built-in support for either of those.

gabriele16:06:33

when i try to clj -Spom the deps.edn in the guide of ion i get

Caused by: org.eclipse.aether.resolution.ArtifactResolutionException: Could not transfer artifact com.datomic:ion:pom:0.9.7 from/to datomic-cloud (): Access Denied (Service: Amazon S3; Status Code: 403; Error Code: AccessDenied; Request ID: 82E950FAE7704579; S3 Extended Request ID: Su8rZMB9c+Z65RQbBwA8K1mxjVkjX0JU5NJRpbrT6k2/rRN8ubyMA3SJO9TucAlKFVf0fQUVZ3w=)
deps.edn
{:paths ["src" "resources"]
 :deps {com.datomic/client-cloud {:mvn/version "0.8.50"}
        com.datomic/ion {:mvn/version "0.9.7"}
        org.clojure/data.json {:mvn/version "0.2.6"}
        org.clojure/clojure {:mvn/version "1.9.0"}}
 :mvn/repos {"datomic-cloud" {:url ""}}
 :aliases
 {:dev {:extra-deps {com.datomic/ion-dev {:mvn/version "0.9.160"}}}}}
what am i doing wrong?

richhickey16:06:42

@kenny right, API gateway has no websockets/sse

Alex Miller (Clojure team)16:06:00

@gabriele.carrettoni do other things (like clj -Spath) work?

gabriele16:06:37

➜  datomic clj -Spath
Error building classpath. Failed to read artifact descriptor for com.datomic:ion:jar:0.9.7
org.eclipse.aether.resolution.ArtifactDescriptorException: Failed to read artifact descriptor for com.datomic:ion:jar:0.9.7
	at org.apache.maven.repository.internal.DefaultArtifactDescriptorReader.loadPom(DefaultArtifactDescriptorReader.java:276)
	at org.apache.maven.repository.internal.DefaultArtifactDescriptorReader.readArtifactDescriptor(DefaultArtifactDescriptorReader.java:192)
	at org.eclipse.aether.internal.impl.DefaultRepositorySystem.readArtifactDescriptor(DefaultRepositorySystem.java:253)
	at clojure.tools.deps.alpha.extensions.maven$eval668$fn__670.invoke(maven.clj:77)
@alexmiller

gabriele16:06:49

same AccessDenied

Alex Miller (Clojure team)16:06:06

I don’t have any issues doing that on my own box, not sure why you’d see that

Alex Miller (Clojure team)16:06:48

I assume you’re not proxied or anything

gabriele16:06:21

@alexmiller i was inside the work vpn, just tried outside of it and i get the same error

gabriele16:06:43

@alexmiller maybe it's picking something from my .aws/credentials? :thinking_face:

Alex Miller (Clojure team)16:06:25

can you try with com.datomic/client-cloud {:mvn/version “0.8.52”} instead ?

Alex Miller (Clojure team)16:06:09

I think prior is a bug in the ion-starter deps.edn, but it’s not this problem

Alex Miller (Clojure team)16:06:23

aws s3 cp . - does that work for you?

gabriele16:06:38

download:  to ./ion-0.9.7.pom

jaret16:06:43

Hi @gabriele.carrettoni can you confirm you have list-buckets with your AWS creds?

aws s3api list-buckets --query "Buckets[].Name"

gabriele16:06:28

yup it works

gabriele16:06:05

and i believe the repo is public so it shouldn't matter if i have or not the permissions :thinking_face:

jaret17:06:34

We updated ion-starter to reflect the latest client. Could you pull the last commit to give you com.datomic/client-cloud {:mvn/version “0.8.54”}?

gabriele18:06:43

same Could not transfer artifact com.datomic:ion:pom:0.9.7 from/to datomic-cloud (<s3://datomic-releases-1fc2183a/maven/releases>): Access Denied (Service: Amazon S3; Status Code: 403; Error Code: AccessDenied; Request ID: 9BF4D9EDB140DB3B; S3 Extended Request ID: KDJi+KjUHOXfcCf0EoU9DSk5V4iP0HxFAUxqj14QFHEwN1rQ5D8qzDOdmCsantCEKzeG+6ydacE=)

stuarthalloway18:06:31

@gabriele.carrettoni I can repro the problem locally, but only when running with no AWS creds at all

stuarthalloway18:06:44

i.e. if I use any random creds from any account things seem to work

gabriele18:06:50

@stuarthalloway on my pc i got it working, i'll try again tomorrow at work to try to understand where the problem is, now i get this error

[ERROR] Failed to execute goal on project ion-starter: Could not resolve dependencies for project ion-starter:ion-starter:jar:0.1.0: Failed to collect dependencies at com.datomic:client-cloud:jar:0.8.54 -> com.datomic:client:jar:0.8.59 -> com.datomic:client-impl-shared:jar:0.8.40: Failed to read artifact descriptor for com.datomic:client-impl-shared:jar:0.8.40: Could not transfer artifact com.datomic:client-impl-shared:pom:0.8.40 from/to datomic-cloud (): Cannot access  with type default using the available connector factories: BasicRepositoryConnectorFactory: Cannot access  using the registered transporter factories: WagonTransporterFactory: java.util.NoSuchElementException

gabriele18:06:21

btw {com.datomic/client-cloud {:mvn/version "0.8.50"} works

Alex Miller (Clojure team)16:06:35

I guess I’d start looking for other environmental variables - default AWS creds, repository settings in ~/.m2/settings.xml, etc. But I’m not sure what in any of those would actually cause a problem.

Alex Miller (Clojure team)16:06:57

the fact that the s3 call works makes me think it’s not the aws creds

kenny16:06:33

I think my question got lost 🙂 Are :cookies supported in the return map for web code? https://docs.datomic.com/cloud/ions/ions-reference.html#web-code

Alex Miller (Clojure team)16:06:42

I think someone is checking

4
stuarthalloway19:06:13

hi @kenny ! in my experience :cookies are derived from the :headers and added by middleware, e.g. https://github.com/ring-clojure/ring/blob/master/ring-core/src/ring/middleware/cookies.clj. I believe that should work fine here.

kenny19:06:35

Right. I guess my question should be rephrased - what subset of the Ring spec is supported?

kenny19:06:31

Is it only :body, :headers, and :status? And will it remain that way?

stuarthalloway19:06:39

At minimum the keys listed at https://docs.datomic.com/cloud/ions/ions-reference.html#web-code. That map is open and can/will get more stuff over time.

Alex Miller (Clojure team)16:06:21

@gabriele.carrettoni I was able to repro it by monkeying with my aws creds, we will check into this

kenny16:06:48

Ion is closed source, correct?

richhickey16:06:43

@kenny yes, it's part of Datomic

kenny16:06:38

Any plans for a cloud agnostic Datomic Cloud?

richhickey17:06:40

@kenny nope, part of the value prop is getting maximum leverage and agility.

johnj17:06:08

are tx functions aws lambdas?

richhickey17:06:48

@lockdown- no, some ions are just used by txes and queries, not behind lambdas

kenny17:06:46

@richhickey your customers aren’t worried about vendor lock-in?

viesti17:06:10

Application Load Balancer (ALB) supports Websockets and lately it got a feature to delegate authentication (https://aws.amazon.com/blogs/aws/built-in-authentication-in-alb/). Do you see possibility to front Ions with ALB?

johnj17:06:16

@richhickey k, cool, just want to use ions for tx functions for now with solo and app code running on a ec2 node

viesti17:06:48

Thinking that API Gateway is public (might change in future though), ALB could be exposed internally only, to allow say access from corporate network only

richhickey17:06:49

@kenny lockin has to be balanced with other objectives. If it dominates ones thinking you'll never maximize your leverage of the platforms. I can't say what's most important for anyone, but there are tradeoffs. We are supporting the 'leverage AWS' strategy. Lots of people succeeding there while others worry and don't ship.

16
Dustin Getz17:06:29

@richhickey Does ion auto scaling apps essentially supersede the client model? Meaning there is no datomic client in play? Just a client-like local api

richhickey17:06:59

@dustingetz right, client-free apps totally possible

Dustin Getz17:06:07

Idiomatic, right?

richhickey17:06:55

well, there's zero reason for clients inside ions, but you may have legacy architecture that dictates use of clients. They're not deprecated or anything

richhickey17:06:13

I think many apps can be written as ions

richhickey17:06:34

and of course you can mix and match

Dustin Getz17:06:47

Are there technical reasons for no entity API or is it just historical at this point, and is the ion "local client api" equivalent in power to the entity api

richhickey17:06:27

@dustingetz there are semantic differences with client (vs peer) that yield 'no entity'. The 'local' API of ions matches the client API so people can do local REPL dev and testing (against cloud). So semantically, it's compatible with 'over wires'. That said, the perf of the same API in local mode within ions trounces clients.

richhickey17:06:28

but keeping wire semantics means if you need to make an architectural shift that requires moving some parts to clients you're not screwed

richhickey17:06:47

thus 'client' is the only API of cloud, ion or out

johnj17:06:53

where does the performance gain comes from? eliminating the network calls?

richhickey17:06:19

@lockdown- right, no wires, same process

Dustin Getz17:06:41

Remote client requesting query for Ion cluster [:find (pull ?f [:db/id *]) :where [(partial contrib.datomic/datomic-entity-successors $) ?succ] [(loom.alg-generic/bf-traverse ?succ :db/ident) [?f ...]]] Ion application local query (->> (loom.alg-generic/bf-traverse (partial contrib.datomic/datomic-entity-successors $) :db/ident) (d/pull-many $ [:db/id *]))

Dustin Getz17:06:04

if I am an Ion app, I can do both, is the former considered idiomatic? Because it is equivalent and works over wires

johnj17:06:42

the former has the overhead of the network, which of the two you use depends on your needs/architecture

richhickey17:06:02

@dustingetz the portability of the former seems like a flexibility win, but you may not care

Dustin Getz17:06:35

@richhickey I like the former but that

Dustin Getz17:06:45

thats a pretty wild "partial" in there

Dustin Getz17:06:57

you would consider that idiomatic? Im ok with that if you are

richhickey17:06:16

idioms take time to develop

richhickey17:06:53

these are some brand new power tools, would hate to pour concrete advice 🙂

val_waeselynck17:06:04

To what extent is it reasonable to query other data stores (e.g a remote SQL or ElasticSearch server) from ions?

richhickey17:06:13

@val_waeselynck you can do anything as long as you give the datomic cluster node's role the necessary permissions

Dustin Getz17:06:28

The ion will autoscale though and the foreign store will not

Dustin Getz17:06:32

you'd need a queue

richhickey17:06:35

your instances, your VPC

stuarthalloway17:06:19

@dustingetz not everyone scales or needs to

stuarthalloway17:06:17

but in any case the important point is, as @richhickey said, it is your instances, use them as you will

val_waeselynck17:06:09

@richhickey thanks, it may be a good idea to make that obvious in the docs - it helps assess the power / limitations, especially since we've been accustomed to "be careful of the code Datomic runs for you" with tx fns

val_waeselynck18:06:11

This is very exciting - it seems to give you the getting started experience of Firebase or Lambda with the scalability of advanced hand-rolled systems

🎉 4
richhickey18:06:44

@val_waeselynck that still applies, you can hold up your txes

val_waeselynck18:06:28

Sure, but from what I understand it's fine for reading and for preparing writes - that part is important but not obvious imho

Dustin Getz18:06:54

If I write to a durable kv store from a transactor fn and it takes 1ms to return, will this in practice slow down transactor throughput? Or are transactions processed in parallel up until the dynamo conditional put which i understand to be batched

richhickey18:06:27

it's going to be serial

Dustin Getz18:06:37

Oh I see, because if there is a cas in the same tx, that needs a dbval

richhickey18:06:46

because e.g. tx fns can query and expect to see prior txes

richhickey18:06:53

yes, stuff like that

richhickey18:06:55

but remember you will likely be initiating your txes from ions, so there's another opportunity there for coordinated (if not transactional) work

Dustin Getz18:06:10

I dont understand that, can you give me another hint

richhickey18:06:34

put in elasticsearch, transact

richhickey18:06:03

means it might be in elasticsearch but tx fails, but not holding up txes

Dustin Getz18:06:43

Oh ok, so if the other store supported two phase commit that might work too

richhickey18:06:03

or whatever, cleanup on tx fail

richhickey18:06:27

you are likely retrying

johnj18:06:40

why web ions and not just a ring app in a lambda function?

richhickey18:06:57

the point is, you will be in your VPC, running in a role, able to do cool things

richhickey18:06:01

@lockdown- a) ease, b) not having to run in lambda execution container, c) running in db context vs making a client call, d) speed

richhickey18:06:51

the important value prop is API Gateway. Putting a lambda behind it is just one option (hint)

johnj18:06:55

ok, pretty cool

Dustin Getz18:06:28

@richhickey Since it is our instance, why is eval blacklisted in :where clause evaluation

Dustin Getz18:06:42

Or is/could that be different in Ion

richhickey18:06:12

there is an :accept list in ion, put stuff there to allow it

Dustin Getz18:06:38

clojure.core/eval was excluded from cloud for security?

Dustin Getz18:06:50

security of your own jars or something

richhickey18:06:52

nothing will run not on the list

richhickey18:06:12

i.e. nothing will be an entry point in tx/query/lambda

richhickey18:06:25

pants on by default

richhickey18:06:12

people inadvertently expose clients, query etc

Dustin Getz18:06:24

Oh, ok makes sense

johnj18:06:36

are there docs that show where web ions might be incompatible with the ring spec?

4
gabriele18:06:19

trying to push

{:command-failed "{:op :push}",
 :causes
 ({:message
   "You must either specify a uname or deploy from clean git commit",
   :class IllegalArgumentException})}
what does it mean? :thinking_face:

gabriele18:06:07

it seems push won't work if there are untracked files, adding them to .gitignore did the trick

Alex Miller (Clojure team)18:06:34

or you can add a :uname key to the op

gabriele18:06:19

thanks, finally got it running 😃

gabriele19:06:06

@alexmiller may i suggest to update https://docs.datomic.com/cloud/ions/ions-tutorial.html#sec-5-4 changing the curl call from

curl https://$(obfuscated-name).
to
curl https://$(obfuscated-name). -d ':hat'
because calling the first gives Expected a request body keyword naming a type

👍 4
kenny19:06:52

Can I specify an AWS profile when pushing or deploying?

jumar20:06:16

@kenny I guess :creds-profile

tony.kay20:06:44

How are people dealing with schema changes in development mode. I’m using conformity, but there is this problem: When I’m developing I might be “fiddling” with some bit of schema. I’d like to put it in a migration, but as soon as I do that it is “fixed” in the database, and I have to “hand unroll” it if I change my mind. It seems: 1. make an “up”/“down” function to use during dev, and drop the “up” one into the migrations only once it is stable. 2. It occurs to me that if you were to look up the tx time of the “up”, you could “undo” all of the changes that had been made since that time (which could be your automatic “down”…`d/as-of` to get the old values for the things the history could tell you have changed since then) Given those two, seems you could just add something like a “SNAPSHOT” versioning in conformity that would automate that on startup in dev mode (e.g. undo everything that has happened since the earliest SNAPSHOT was conformed, then re-conform). Anyone aware of existing code for doing that, or an alternative that is as convenient? Otherwise, we’re probably going to write it.

chrisblom21:06:11

i’ve used datomock to develop migrations on top of an existing db

chrisblom21:06:03

using forked connections to develop the migration, and transact it to actual db when finished

tony.kay21:06:25

nice..I’l look at that

chrisblom21:06:20

i’ve even used it to test migrations in production, by connecting to the production db, forking the conn, and running the migrations and app with the forked conn

eggsyntax21:06:44

Seconded on datomock, it makes datomic development workflow a joy ❤️

tony.kay21:06:47

yeah, I think that looks pretty good, actually.

tony.kay21:06:17

Super cool idea…and only about 100 LOC…it’s all about finding the right abstraction on top of your tool

💯 12
tony.kay21:06:30

glad I asked. Thanks!