This creates a new codegen module music-gen, in the libs directory. The gen postfix is added to generated modules, to distinguish them from other modules, coded by hand.
pnx g @logosphere/sdk:module --name music
Take a look at the project structure under libs/music-gen/src
Edit model file
In this step, we will create a simple model for the music domain, consisting of Artist, Album and Track entities, where one artist can have multiple albums and one album can have multiple tracks. We also have a predefined enumeration for genres.
The *.model.ts file that is pre-generated with a few sample entities, should be the only modifiable file in a codegen library. The rest of the code assets will be generated from the model defined in this file.
The model is based on TypeScript classes and is very similar to code-first modelling in other TypeScript frameworks, such as NestJS, TypeORM etc. Every entity, as per DDD definition of an entity is defined as a class with an @Ent() decorator and every property within an entity should be decorated with a @Prop() decorator. Enumerations should be registered with registerEnum() function.
For the complete list of model properties check the Logosphere Modeling documentation.
Modify libs/music-gen/src/music.model.ts file to add the following entities. Delete generated MusicEntity. Leave Wallet, WalletAsset, and User entities unmodified.
/* eslint-disable @typescript-eslint/no-unused-vars */import { Ent, Prop, registerEnum } from'@logosphere/sdk';exportenumGenre{ Pop =0, Rock, HipHop, RnB, Country, KPop, JPop, World};registerEnum(Genre,'Genre');@Ent('artist')exportclassArtist { @Prop({ examples: ['Taylor Swift','Lil Nas X','Dua Lipa'], }) name:string; @Prop({ examples: ['About Taylor Swift','Lil Nas X Bio','Dua Lipa is awesome'], }) about:string;}@Ent('album')exportclassAlbum { @Prop({ examples: ['Fearless','MONTERO','Future Nostalgia'], }) name:string; @Prop({ type: () => Genre }) genre:Genre; @Prop({ type: () => Artist, index:false }) artist:Artist;}@Ent('track')exportclassTrack { @Prop({ examples: ['Shake If Off','Montero','Levitating'], }) name:string; @Prop({ type: () => Genre }) genre:Genre; @Prop({ examples: ['Be yourself and shake it off','Also called call me by your name','Just a great song'], }) description:string; @Prop({ type: () => Album, index:false }) album:Album; @Prop({ type: () => Artist, index:false }) artist:Artist;}@Ent('playlist')exportclassPlaylist { @Prop({ examples: ['Top 40','Best of Pop','New releases'], }) name:string; @Prop({ type: () => [Track], index:false}) tracks:Track[];}@Ent('walletAsset')classWalletAsset { @Prop({ doc:'Name of the asset', examples: ['4269736f6e'] }) name:string; @Prop({ doc:'Fingerprint of the asset', examples: ['asset12q7zh30hj2yme96wy8ms4fcdrwtep0auz8xqly'], }) fingerprint:string; @Prop({ doc:'Policy ID of the asset', examples: ['0b7018936bc41808ddabd96b4908b583195a0c252b5752ad38012bdb'], }) policyId:string; @Prop({ doc:'Quantity of the asset', examples: ['1'] }) quantity:number; @Prop({ doc:'Metadata associated with the asset' }) metadata:string; @Prop({ doc:'Fluree subject ID of the asset', examples: ['87960930223082'] }) assetSubjectId:string; @Prop({ doc:'Logosphere ID of the asset', examples: ['62c0ac76d6eebbf70828da57ea06c41a55001a2eb3cc929206d8f39abdbfaefc', ], }) logosphereId:string;}@Ent('wallet')classWallet { @Prop({ doc:'Name of the wallet', examples: ['Babingos wallet'], }) name:string; @Prop({ doc:'ID of the wallet', index:true, unique:true, examples: ['cd72843c95467883ccd6dafe227b91c96f071713'], }) walletId:string; @Prop({ doc:'Address of the wallet', index:true, unique:true, examples: ['addr_test1qzsn8km55mp6cra7l20cymauks2fay8sqc3jr874mgmxpsa5mj4dvw5zrxmhauknj60c8tsf7x72ng0r8zmxa3necjlsgx9q6d', ], }) address:string; @Prop({ doc:'Public key of the wallet', index:true, unique:true, examples: [ 'a1f009e6f5770c7b10729f27237c7ccc677739e31119a69766664dee611220948234926b2c445c2e9e2ff40f22beafa193d7fedf72e5e877bffd606d33b6638c',
], }) publicKey:string; @Prop({ doc:'Balance of the wallet in lovelace', examples: ['0','1000' ], }) balance:number; @Prop({ doc:'Wallet assets',type: () => [WalletAsset], }) assets:WalletAsset[];}@Ent('user')classUser { @Prop({ doc:'Username of the user', index:true, unique:true, examples: ['babingo_whoelse'], }) username:string; @Prop({ doc:'User wallet',type: () => Wallet, }) wallet:Wallet;}
Generate API
Build model with the following command:
pnx affected:build
NX automatically tracks changes and only calls build target for affected libs. Alternatively you can build the codegen module with:
pnx build music-gen
Now you can generate GraphQL API with DDD assets architecture:
pnxg@logosphere/sdk:api--modulemusic
After executing this command, you will see your music codegen module library populated with DDD assets and the two applications generated under apps folder:
apps/music-e2e
apps/music
The music-e2e is an app for running integration tests against it.
The music-app is an application that is built into a docker container and serves the GQL API
Create environment variables and docker-compose
To run, the application require set of environment variables and number of external services. To generate pre-configured .env and docker-compose files call the command below
pnx g @logosphere/sdk:docker-compose
If you already have one of generated files, the generator will ask you about override. In case of confirm the previous configuration will be lost
Shell environment variables
These environment variables will be used with the Logosphere NX plugin generators pnx g command and should be defined in a shell config file, such as .zshrc or in a file, such as .env-sh which should be sourced with source .env-sh
# FLUREE# URL of the Fluree DB instanceexport FLUREE_URL='http://localhost:8090'# Fluree ledger in the format <network>/<ledger>export FLUREE_LEDGER='local/music'# Flag to update Fluree schema automatically when the model is changedexport FLUREE_AUTO_UPDATE_SCHEMA=true
Run single Docker image
Modify tags option in the docker target in the apps/music/project.json from logosphere to your DockerHub organization:
Copy & paste the trackFindAll query into the Apollo Sandbox
querytrackFindAll { trackFindAll { id subjectId name genre description createdAt updatedAt }}
Expectedly it should return empty result, since there is no data in the ledger yet.
Create a track
Copy & paste the trackSave mutation and variables into the Apollo Sandbox
mutationtrackCreate($track: TrackInput!) { trackSave(track: $track) { id subjectId name genre album { id name artist { id name about } } description createdAt updatedAt }}
Variables:
{"track": {"name":"Shake it off","genre":"Pop","description":"Song about shaking it off","album": {"name":"1989","genre":"Pop","artist": {"name":"Taylor Swift","about":"American pop singer" } } }}
Output:
{"data": {"trackSave": {"id":"4234360906fdbc47726ba53cf5833a673ef28654e5db7bcbc5ba68a887cce8d9","subjectId":"387028092977152","name":"Shake it off","genre":"Pop","album": {"id":"b5726dacdc735d08fcdc08d73de740ed00067895cb5fbc24beb1cf33a321a335","name":"1989","artist": {"name":"Taylor Swift","about":"American pop singer" } },"description":"Song about shaking it off","createdAt":"2022-07-20T21:50:51.085Z","updatedAt":"2022-07-20T21:50:51.085Z" } }}