• Welcome to the new COTI server. We've moved the Citizens to a new server. Please let us know in the COTI Website issue forum if you find any problems.

Starship Design using REST Microservices

If you're just running the service locally, then what's the point?

It's loose coupling taken to a logical extreme.

It forces me to think about starship design as a set of productions, if you will, in the form of perhaps-ridiculously-small REST services.

It's a way to incrementally build something out.

It's a way to minimize serious architectural flaws that force me to scrap-it-and-start-over.

It's a way to divide and conquer what is frankly a large programming task.

It's a way to bottom-up work on the codebase that maybe avoids creating chaos.

It's a way to turn starship design into an API contract that can be easily tested based on individual requirements.

"But it's just starship design!" -- said by no-one who has tried it.
 
You are missing some of the point of micro-services. Each part of the app is hosted separately, potentially by different providers. Micro-services are typically small, easily hosted on free host services (at small scales like for Traveller use).

It still has to be hosted. Someone has to choose to take on the burden of finding a provider, standing it up, and maintaining it. Which means that it has a lifespan directly correlated to however long the person wants to maintain it. Whether that be days, weeks, months, or years.

And it suffers from the singular attribute of networks that folks tend to always forget.

They're unreliable, and that's just the connectivity part.

Samsung is shutting down its SmartThing hub this month.

https://arstechnica.com/gadgets/2021/06/samsung-is-killing-the-first-gen-smartthings-hub-this-month/

Millions of dollars of hardware from a multi-billion dollar mega corp, going poof. Like Keyser Soze. "And like that...he is gone."

What happens when Marc gets tired of this bulletin board? Or aramis? or the person who runs Travellermap?

What have we learned about the always on, never forgetting internet? That's it's all a lie. An illusion. It's laced with bit rot, and weak broken dependencies. In some spaces, building 10 year old code is quite challenging. Entire communities vanish overnight.

Thank heavens for archive.org, as incomplete as it is.

And, frankly, I don't know how well something like a Docker image ages. Will one from 5 years ago still work? I have no idea. Plus we're in a growing age where Intel is fighting the rise of ARM infrastructure. Apple is switching over wholesale. Running a Docker image on a Mac is challenging enough, I don't know if you can run a Docker image on Windows, and will I be able to run a Docker image on a new M1 based Mac? I have no idea.

Then, of course, the client code has to be configured to route properly to these services. Say travellermap vanishes one day, but, since it's been open sourced, someone else decides to host it. Now all of the URLs change in the client code "that worked for years(tm)". Maybe the port has to change. Perhaps, especially in this modern environment, new certificates need to cut and and exchanged. All of that legacy code suddenly "broken".

Yet, the LBBs I have from 1977, scuffed, dog eared, and worn as they are. Those still work.

It's a fine intellectual exercise. Whether something as integrated as starship design can work with a coarse service framework. I honestly don't know, I don't think it can be done with just a bunch of individual services vs something that operates on the whole.

Simply changing the hull size recomputes almost every major formula on a ship, and potentially impacts most every major design rule. That means while you could have a service that given hull size and, say, TL, you might get back a price, or mass, or something else, that's not enough. The ripple effect is legion, and the burden of managing that ripple should not be on the client.

That's a contrived example, of course.

Finally, the true definition of interoperability is not implementations per se but documented protocols, format, and APIs. Obviously that has to start somewhere. And ad hoc can become de facto and eventually de rigueur.

At a logical level, you can design around such services without relying on a network infrastructure. RPC architectures have been hiding that stuff for years, from RPC, to CORBA, to SOAP being rendered as high level language API calls. But in the end, at the local code level, it doesn't look any different than a normal function or method invocation. So, then you go "hmm", what's really being achieved here.

I spin around on this myself. My current path is a local program feeding a SQL database that can be readily queried ad hoc as an integration point, rather than having the client program host services. I'm on the edge of publishing stored procedures in the database so to offer a more "API" experience.

That way, you have the client program, a database image, and then you can you access the db with a 3rd party program, and at least have access to the raw data, and perhaps use the stored procedures to manipulate it at a high level. (Starting up the DB is straightforward, we're not spooling up Postgres for SQLServer here...)

It's fair to say the difference between that and hosting web service calls in the client, at 30,000 feet, it's pretty much the same. The web service API may be better in the long run.

But short term, having access to the DB I think has high value and low cost on me as a developer.
 
Would you implement the ship design process as event-driven? I could see the current state of a ship design as a stream of choices and decisions, including assignment of required minimum or maximum values against which the design is validated.

set_ruleset "T5.10"
set_requirement min_cargo_cap 200
set_value hull_configuration "streamlined"
set_requirement jump_number 3

The first services I'd implement are to (1) initialize an empty event stream or fork a new one from an existing checkpoint for refit and modification of a class, (2) project the stream of design events into a ship design document , (3) validate the document against the rules and design requirements, and (4) format the the document for checkpoint, export or display.

The second and third services could be further decomposed into a calls to more specific services.
 
Last edited:
What have we learned about the always on, never forgetting internet? That's it's all a lie. An illusion. It's laced with bit rot, and weak broken dependencies. In some spaces, building 10 year old code is quite challenging. Entire communities vanish overnight.

Quite, that is huge.

But the stated purpose:
Unless the purpose is to play with and learn the new hotness, of course.
Exactly.

The new hotness is never stable, and will generally not work well ten years from now, as robject found out with his previous software.


If you just wanted it to work, C/C++ or perhaps Java (without any specific GUI APIs) would be quite stable. Save to human-readable text file without embellishments, no XML, no JSON, no nothing. Utterly boring, utterly dependable...


I have spreadsheets from the 90's made in MacOS 6/68000, worked in MacOS 7/PowerPC, worked in MacOS X/Power PC, worked in MacOS X/Intel32, works in MacOS X/Intel64, and presumably works in MacOS XI/ARM. They work equally well under various generations of Windows and Linux.

But new and hot, it is not, quite the opposite, and weren't 30 years ago either.
 
I understand that most of this discussion is heavily tech-dependent.

Some of it could be informational.

But mostly it's a diversion from the task at hand.

But, I get the pros and cons. I've lived the latter more than the former, so I get that.

I'd never go through the headache of standing up a server on paid-for host time. Luckily, we have much easier options now.
 
Creating downloadable apps is absolutely fine. The main thing is to get started and down the track hopefully have something to share.
 
I've updated the OP with my concept. I'll post the link here too.

https://github.com/bobbyjim/acs-builder

Builds (some) sensors to order (sans Stage) and no error checking. Feel free to abuse it.

Not clean. Yet. Lots to do.

I have some learning to do, to debug the Dockerfile, which is full of my ignorance, and therefore doesn't work (yet).
 
Seems to me that Golang is nicely suited for this task. Light (=one external package) and implementing REST seems easy.

Certainly not the only way. I'd be more comfortable with Perl. Even Java would feel more familiar, but I am assuming that Java is overkill. Python has ways of doing this too. And the list goes on.

We'll see.

One nice side effect of REST services is that the implementation language "doesn't matter". The API is the contract.
 
And, with what I know currently about Docker, I think that is ideally suited to distribute the service, once I know what I'm doing there.

I can shove the application into a multi-stage build container thing (apparently keeps the container image size suitably small), push it to Dockerhub, and then you can "deploy" and run it locally straight from Docker.

Egads that's an easy deploy model.
 
In the meantime, here's some tests of my microservice. I used Postman to build up a query stack.

Code:
GET localhost:1317/sensors
This fetches all sensors.

Code:
{"A":{"type":"A","name":"Activity Sensor","tl":11,"mcr":0.1,"rangeClass":"R","mountClass":"Surf"},"C":{"type":"C","name":"Communicator","tl":8,"mcr":1,"rangeClass":"S","mountClass":"Surf"},"E":{"type":"E","name":"EMS","tl":12,"mcr":1,"rangeClass":"S","mountClass":"Surf"},"G":{"type":"G","name":"Grav Sensor","tl":13,"mcr":1,"rangeClass":"S","mountClass":"Surf"},"H":{"type":"H","name":"HoloVisor","tl":18,"mcr":1,"rangeClass":"S","mountClass":"Surf"},"N":{"type":"N","name":"Neutrino Detector","tl":10,"mcr":1,"rangeClass":"S","mountClass":"Surf"},"R":{"type":"R","name":"Radar","tl":9,"mcr":1,"rangeClass":"S","mountClass":"Surf"},"S":{"type":"S","name":"Scanner","tl":19,"mcr":1,"rangeClass":"S","mountClass":"Surf"},"T":{"type":"T","name":"Scope","tl":9,"mcr":1,"rangeClass":"S","mountClass":"Surf"},"V":{"type":"V","name":"Visor","tl":14,"mcr":1,"rangeClass":"S","mountClass":"Surf"},"W":{"type":"W","name":"CommPlus","tl":15,"mcr":1,"rangeClass":"S","mountClass":"Surf"}}

And there they all are. There's enough data to make a decision about which one you want.
We do need a mount for it, so let's see what we have available:

Code:
GET localhost:1317/mounts

And that returns:
Code:
{"B1":{"type":"B1","tons":3,"mcr":5,"class":"Barbette"},"B2":{"type":"B2","tons":6,"mcr":7,"class":"Barbette"},"Surf":{"type":"Surf","tons":0,"mcr":0,"class":"Surface"},"T1":{"type":"T1","tons":1,"mcr":0.2,"class":"Turret"},"T2":{"type":"T2","tons":1,"mcr":0.6,"class":"Turret"},"T3":{"type":"T3","tons":1,"mcr":1,"class":"Turret"},"T4":{"type":"T4","tons":1,"mcr":1.5,"class":"Turret"}}


How about ranges, though? Here's a GET to fetch all available ranges.

Code:
GET localhost:1317/ranges

And the ranges pop back out:
Code:
{"AR":{"type":"AR","tlmod":0,"costmod":1,"tonsmod":1},"BR":{"type":"BR","tlmod":-3,"costmod":0.25,"tonsmod":0.25},"D":{"type":"D","tlmod":-1,"costmod":0.5,"tonsmod":0.5},"DS":{"type":"DS","tlmod":2,"costmod":5,"tonsmod":3},"FR":{"type":"FR","tlmod":-2,"costmod":0.33,"tonsmod":0.33},"Fo":{"type":"Fo","tlmod":2,"costmod":5,"tonsmod":3},"G":{"type":"G","tlmod":3,"costmod":6,"tonsmod":4},"LR":{"type":"LR","tlmod":1,"costmod":3,"tonsmod":2},"Or":{"type":"Or","tlmod":1,"costmod":3,"tonsmod":2},"SR":{"type":"SR","tlmod":-1,"costmod":0.5,"tonsmod":0.5},"Vd":{"type":"Vd","tlmod":0,"costmod":1,"tonsmod":1},"Vl":{"type":"Vl","tlmod":-2,"costmod":0.33,"tonsmod":0.33}}

At this point, you know enough to build a sensor. I'm going to build a Neutrino Sensor, and I'm going to put it in a single barbette, and I want it to have Deep Space range:

Code:
POST localhost:1317/sensors/N

{
    "mount": "B1",
    "range": "DS"
}

This returns my sensor, which is:
Code:
{
    "type": "N",
    "name": "Neutrino Detector",
    "label": "DS B1 Neutrino Detector-12",
    "stage": "Std",
    "mount": "B1",
    "range": "DS",
    "tl": 12,
    "tons": 9,
    "mcr": 26
}
 
Note that much of this process is data-driven. My UI would have to know the REST API defined by this service. It will also have to know some things about Traveller -- it has to know about sensors and mounts and ranges. It has to know how to show a component list and roll up volume and cost. It will still need to do some analysis (but the microservice could do that, too). But the actual components have been decoupled from the UI.

For example, the UI doesn't know how to put sensors together, except to get a type, a mount, and a range from the user. It makes calls to fetch the data. It will have to know how to format that data into radio button groups or selection lists or something. But it doesn't care what the data actually is.

It only has to know what the user needs, and what the API needs. In this respect, the UI is like a broker.
 
Now I'll build a hull. First I'll get the available hull types.

Code:
GET localhost:1317/hulls

Code:
{"A":{"config":"A","name":"Airframed","tl":0,"tons":0,"mcr":2,"mcrPer100Tons":7},"B":{"config":"B","name":"Braced","tl":0,"tons":0,"mcr":0,"mcrPer100Tons":3},"C":{"config":"C","name":"Closed Structure","tl":0,"tons":0,"mcr":0,"mcrPer100Tons":2},"L":{"config":"L","name":"Lifting Body","tl":0,"tons":0,"mcr":4,"mcrPer100Tons":12},"P":{"config":"P","name":"Planetoid","tl":0,"tons":0,"mcr":0,"mcrPer100Tons":1},"S":{"config":"S","name":"Streamlined","tl":0,"tons":0,"mcr":2,"mcrPer100Tons":6},"U":{"config":"U","name":"Unstreamlined","tl":0,"tons":0,"mcr":2,"mcrPer100Tons":3}}

Let's say I pick an Airframe hull, and want to build a 500 ton one at TL 10.

Code:
POST localhost:1317/hulls/A

{
    "tons": 500,
    "tl": 10
}

My service responds with
Code:
{
    "config": "A",
    "name": "Airframed",
    "tl": 10,
    "tons": 500,
    "mcr": 37,
    "mcrPer100Tons": 7
}

It's all fine, but I think that "mcrPer100Tons" thing doesn't need to be there. The rest is good.
 
Now I want to build drives for my 500t airframe hull. Drives available are:

Code:
GET localhost:1317/drives

Code:
{"J":{"type":"J","name":"Jump","label":"","rating":2.5,"tons":5,"mcr":0,"targetHullTons":0,"tonsMinimum":10,"mcrPerTon":1,"fuel":10},"M":{"type":"M","name":"Maneuver","label":"","rating":2,"tons":0,"mcr":0,"targetHullTons":0,"tonsMinimum":2,"mcrPerTon":2,"fuel":0},"P":{"type":"P","name":"Powerplant","label":"","rating":1.5,"tons":1,"mcr":0,"targetHullTons":0,"tonsMinimum":4,"mcrPerTon":1,"fuel":1}}

Say my UI lets me select Jump. It would also have to ask me for the drive rating... I'd select a "3". The UI would have to know how to build that payload, as usual. But otherwise I think I've got everything I need:

Code:
POST localhost:1317/drives/J

{
    "targetHullTons": 500,
    "rating": 3
}

Code:
{
    "type": "J",
    "name": "Jump",
    "label": "Jump-3",
    "rating": 3,
    "tons": 42,
    "mcr": 42,
    "targetHullTons": 500,
    "tonsMinimum": 10,
    "mcrPerTon": 1,
    "fuel": 150
}
 
Back
Top