Beans in Lucee 6

components can do A LOT of stuff, they can be used for Soap, Rest, Hibernate and (with Lucee 6) web socket interactions. In addition they allow code injection, extend other components and implement interfaces at runtime and many more stuff.
Problem is that every component has the burden to support all of this, even it does not do it.
We changed in Lucee 6 how a component is setup, so it not always comes with all features out of the box.
If a component for example does not extend any other component, Lucee does setup it differently. If a component does not get code injected it works differently…
Of course the only different for the user is, that the component get faster.

This is a great start, but still comes with limitation, because Lucee is a dynamic language.
So the idea wa born for “Beans”. The most stupid and simple component you can think of, a mix between components and struct.

So how does a Bean look like

// Person.cfc
bean {
   property string firstName;
   property string middleName;
   property string lastName;
  
   function getLastName() {
       if(isNull(this.lastName)) return "NoName";
       return this.lastName;
   }
}

// index.cfm
person=new Person();
person.firstName="Susi";
person.lastName="Sorglos";
dump(person.firstName&" "&person.lastName):

A bean can only define properties and getters and setters to this properties. It cannot extend or implement components or interface (maybe other beans?). It has no variables scope, only the private this scope. functions defined must be public.
From outside it only has the Struct interface, so you cannot call functions.
All this limitation makes it possible to make them very fast and much less memory consuming.

The idea is also that you can define a bean as a return type for a query

query name="persons" returntype="Person" {
echo("select firstname,middlename,lastname from person");
}
dump(persons);

Benefit with this over struct as a return type, you can pass along that Person array and you do not have to check if firstname exists when you use it.

When you convert to a json string it will create

{'firstName':"Susi",'lastname':"Sorglos"}

What do you think, useful , useless or i don’t care?

3 Likes

I like it so far, and would add one feature (I know , I know!!!) that is to have accessors=true enabled by default. So you can do getFirstName() and any other function. I think that is the main use case for this type of component (or maybe it’s just me)

I would maybe also restrict the init method? So you know if you do:
mark = new Person(firstName: "Mark", middleName: "Awesome", lastName: "Drew");
it would always fill the properties so you dont have to do bootstrap code

yes of course the init I have forgotten to mention, that must be supported, no question exactly the way as you pointed out. You can do a init function for custom assignment, but it is optional. you also simply can define the properties as you did, lucee then will assign them.

getters and setters are a concept introduced in java (feel free to correct me if i’m wrong and java simply did adapt it from elsewhere).
They did create a name pattern (set,get and is) because the language did not support it natively. So it was and still is just an idea used on top of the language, not baked in. CFML actually supports that concept from java natively. let say you have a java bean that looks like this:

package org.lucee.examples;
	public class Person {
		private String lastName;

		public Person(String lastName) {
			this.lastName = lastName;
		}

		public String getLastName() {
			return lastName;
		}

		public void setLastName(String lastName) {
			this.lastName = lastName;
		}
	}

in CFML (Lucee and ACF) you then can use an instance of Person like this

SYNTAX 1
person=createObject("java","org.lucee.examples.Person").init("Sorglos");
person.lastName="Lustig";
dump(person.lastName);

this code implicitly calls the setters and getters, sure you can also do

SYNTAX 2
person=createObject("java","org.lucee.examples.Person").init("Sorglos");
person.setLastName("Lustig");
dump(person.getLastName());

But imported point is, both examples (Syntax 1 and 2) do exactly the same. Both do call the setter and getter.

Same idea for Lucee beans, when you do

p=new Person();
p.lastName="Sorglos"; // syntax 1
// or
p.setLastName("Sorglos"); // syntax 2

it would be exactly the same in the back.

Question is, do we really need “syntax 2” in that case, sure we can add the interface “Objects”

and if we do, that would not even be a performance issue.

But that will bring one logical problem, when you can do person.getLastName(), people then maybe expect to be able to do the following (because in CFML functions are objects).

lastNameFunc=person.getLastName;
dump(lastNameFunc());

But for me that is not even the main issue, for me the java getter/setter pattern is simply coming from a limitation we do not have in Lucee. where is the benefit in Syntax 2, when we have Syntax 1?

Does this mean at the person bean can not have a (public) function getFullName() which would return firstName + middleName + lastName?

Would the bean have a toString() function built-in for serialisation - like when dump of a bean variable?
I would expect the bean author would need to created this function.

Will this syntax also work:
person['firstName'] = 'Andrew';

If a bean is using the THIS scope, then getters and setters do not seem to be necessary, but using setters mean the set function could process the input. From previous discussions with @markdrew, I thought one of the benefits for a bean was it would have some smarts for doing things like validations. So, for example, the person bean could enforce firstName and LastName being mandatory, and middleName being optional ( and therefore firstName and lastName would need to be passed in init() ).

Rather than a separate syntax for what is essentially type checked structs, I would prefer to see component constructor enhancements for “correct by construction” coding style. Some example issues

Consider this component:

/**
 * BasicComponentWithRequiredField.cfc
*/
component accessors="true" {
    property name="Name" type="string" required="true";
    property name="CreatedAt" type="date";
}

And this component:

/**
 * BasicComponentWithInit.cfc
*/
component accessors="true" {
    property name="Name" type="string" required="true";
    property name="CreatedAt" type="date";

    public function init(){
        //Should be able to do additional checks here
    }
}

Here are a sampling of issues and things that prohibit correct by construction:

//This correctly populates the field with the passed in values but there is no way to have a
//constructor that does additional validation (see BasicComponentWithInit below)
var BasicComponentWithRequiredField = new BasicComponentWithRequiredField(Name = "Foo", CreatedAt=now());
writeDump(BasicComponentWithRequiredField);


//This should fail because a required property was not passed in but it does not fail
var BasicComponentWithRequiredField = new BasicComponentWithRequiredField();
writeDump(BasicComponentWithRequiredField);

//This should fail because CreatedAt is not a date but it populates CreatedAt as null instead
var BasicComponentWithRequiredField = new BasicComponentWithRequiredField(Name = "Foo", CreatedAt="Foo");
writeDump(BasicComponentWithRequiredField);

//Property population no longer works when there is an init method. This means now you
//have to duplicate all of the properies in the init() arguments, and then set into variables,
//just to have a few extra checks at construction time
var BasicComponentWithInit = new BasicComponentWithInit(Name = "Foo", CreatedAt=now());
writeDump(BasicComponentWithInit);

So here is all the boilerplate you have to do to properly create a fully validated component, which becomes a lot when there are a lot of fields:

/**
 * BasicComponentComplete.cfc
*/
component accessors="true" {
    property name="Name" type="string" required="true";
    property name="CreatedAt" type="date";

    public function init(required string Name, date CreatedAt=now()){
        variables.Name = arguments.Name;
        variables.CreatedAt = arguments.CreatedAt;
        //Do any additional validation here
    }
}
1 Like

I remember when Adobe ColdFusion 9 was in alpha-- with the addition of Hibernate and getting back arrays of objects, there was early talk of a new lightweight CFC bean with limited functionality which would be faster. This idea was scrapped by the public beta if I recall and I was sort of happy they didn’t do it. I don’t like having two constructs in a language for the same thing and again, this feels like we’re forcing the language to bow to our performance issues instead of designing the language the way that makes the most sense.

  • If a CFC doesn’t use extends, then no overhead for inheritance should exist.
  • If a CFC doesn’t use implements, then no overhead for interfaces should exist.
  • If a CFC doesn’t use access="remote" or rest="true" then no overhead for SOAP or REST should exist
  • If a CFC doesn’t use persistent="true" then no overhead for ORM should exist
  • CFML is a dynamic language, so always expect to be able to inject things dynamically. That should never go away.

Instead of creating a new construct, let’s just make the base CFC as fast as possible. The engine should be able to optimize what additional feature are necessary based on the metadata of the component at run time.


This also reminds me of an idea I’ve had for a long time in which it would be nice if a UDF set into a struct could access the struct directly as this so I could do something very simple like:

person = {
  fname : 'brad',
  lname : 'wood',
  getName : function(){ return this.fname + ' ' + this.lname; }
}

person.lname = 'Foobar'
echo( person.getName() ) // Outputs "Brad Foobar"

Perhaps this would need to be something else like context to preserve backwards compat for a UDF in a struct in a CFC. But either way, my point is I’d rather see us add a small amount of sugar to good old basic structs to allow self-referencing like JavaScript allows, than to diverge the idea of a CFC or add another language construct that’s in the middle. (Fun fact, my example above will work in JS if you replace the & with + for concatenation!)

1 Like

All of course is up for debate, my answer is based on what is planned so far.

So far the idea is that you cannot do standalone getter/setter, you always need a matching property. If you want to have smartness you should use components. But sure you can overwrite the default getter/setter by defining you own, but like i said, that only works for properties.

We will support Implicit Conversion Functions
So you can do

  • _toBoolean()
  • _toDate()
  • _toNumeric()
  • _toString()
  • _toJSON()
    It will support convert to json and to string out of the box and of course dump will show a nice view.

yes of course

good point, because you not necessarly have to make a init method to populate a bean, we could simply do it like this

bean {
   property name="firstName" required=true;
   property name="middleName" required=false;
   property name="lastName" required=true;
}
... 
new Person(firstName:"Susi",lastName:"Sorglos");

like with getter and setters, you also can do a init function to customize the input

bean {
   property name="firstName" required=true;
   property name="middleName" required=false;
   property name="lastName" required=true;

   function init(required firstName, middleName="", required lastName) {
      if(firstName=="Susi") throw "I don't like that name sorry!";
   }
}
... 
new Person(firstName:"Susi",lastName:"Sorglos");

the idea of beans is like having a struct with a fix set of properties, sure they also can have fix types, more important is the fix properties. We also improved struct, they come now with a “onMissingKey” listener, but that is another story :wink:

We can also discuss about extending components of course, but focus of beans are speed and we only get that speed by creating components that are missing 80% of the features. It is all about removing functionality, not adding :wink:

As I have written above, we did this as much as possible already and we also improved components in some places on that behalf. Of course that also was a big focus when we implemented components. components make for example sure that they not have to search multiple places for variables when you have a components that extends others.

But let me show one reason why components cannot be as fast as beans.

component assessor=true {
   property name="lastname";
   property name="firstname";
}
...
p=new Person();
dump(p.getLastName());

In that case Lucee creates 4 functions (2 getter and 2 setter), sure they are much faster than regular functions, because we do not have to create and destroy an local and argument scope, but we still have to provide functions, because you can do

p=new Person();
test=p.getPerson;
dump(test());

That is the reason i do NOT wanna provide getter and setter functions from outside for beans.
As long the user does not create a custom getter/setter, i can make that access much faster. and they are not necessary anyway.
This is one of multiple example of a benefit by reducing the interfaces.

For me performance is only a nice side effect (sure essential for beans as a return type for queries).
Actually i think they are a very nice addition, i love the idea of having a clear typed struct like object.

yeah, like javascript, that is also something i did consider. but there are 2 major issues with that.
In contrast to javascript in CFML only functions and components have context (scope) switch, in javascript every block has it’s own context. so you would need to have a context switch within the literal struct (at least), that would make every literal struct creation slower and using more memory.
what if you do

person = {
  fname : 'brad',
  lname : 'wood',
}
person.getName : function(){ return this.fname + ' ' + this.lname; };
function setLName(lname) {
    this.lname=arguments.lname;
}
dump(person.getName());
person.setLName=setLName;
person.setLName("Sorglos");
dump(person.lname);

you expect that to work as well?

If that should work we have to add a context switch every time you access one of this functions, but that is not possible, because accessing a function inside a struct is completely different to access a component. you do not enter the struct do execute a funciton, it is more like a take away not a restaurant. a struct only has getters and setters, no invoke functions. Sure we could make that happen, but then ALL structs get slower, for the same reasons components are slower than the planned beans.

Of course that functionality would also be completely unrelated to a bean implementation.

as you pointed out yourself, this would break

What we will have :wink:
As i pointed out above, we added support for “onMissingKey” to structs and “onMissingIndex” to array (with no performance downside) that is extremely helpful, but that will be part of another thread.

Can you explain what you mean by “context switch”. You used that phrase a few times but I’m not sure if you’re just referring to an “if” statement somewhere in Lucee’s code or something else. Also, why would struct creation be slower? And why struct literals specifically? I would think it would work regardless of how the struct were created and would only affect calling a UDF that lives in a struct, nothing else. And why would it be using more memory? No additional scopes would need to be created or managed, I just imagined it being like this:

  • Right now, a UDF “knows” if it lives inside a CFC and when that UDF resolves variables, it looks in its “owning” CFC to try to find them.
  • So the suggested change would be that UDFs wouold also be able to “know” if they were living in a struct. And when they went to look for a variable, one of the places they would check would be the struct. In fact, if it is an explicit scope such as context.foo then this should be very performant since the UDF would know exactly where to look.

This would require the UDF to know where it lives and some conditional logic to know where to look up variables, but I’m unclear where the extra memory would be going.

I’m not sure what you are referring to there. I did say there could be some ambiguity using this to reference the struct’s data, but that is easily worked around by using another name such as context.foo.

Theoretically I get the point of ‘beans are speed’ but practically I don’t see where I’d use them because they are too ‘in the middle’. If I’m creating a lot of data and my performance constraints are too much for components, I drop into arrays or structs. If I’m not creating a lot of data, I prefer correctness over performance, and I’d like more features as mentioned. So beans seem to sit in the middle where they don’t provide the full correctness, don’t help with domain modelling, yet they are a different type of object semantics. The only place I could see them useful is as Data Transfer Objects, which can already be achieved with barebones components. I’ve never run into a performance problem with components as DTOs myself. I’ve run into performance problems creating a lot of fully correct and validated components in the ‘correct by construction’ style I mentioned.

If beans were just an extension of a struct that allowed hooking into and customize the behavior of a struct (like you mention onMissignKey) being able to do, that could be useful. So looking forward to these struct enhancements.

Also Java is introducing ‘records’ which are light classes intended to assist modelling domain data. Inspiration and use cases may be found there.

1 Like

Unless there is a measurable performance benefit for this over Structs (as opposed to over regular components) - I’m somewhere between “useless” and “I don’t care” on this one as I don’t see good use cases for it over Structs at this time.

If it only solves the problem of having a known set of keys then maybe that can be done with specialized Struct type?

If the properties can become final, i.e. set once and then unmodifiable then I can see some benefit here, though we can do that for Structs too.

I like the addition of onMissingKey listener to Structs, but I’m not sure if that’s used in put, get, or both? In Java we have the methods (which should probably be migrated to a new thread):

  • getOrDefault() like Java [1]
  • putIfAbsent() like Java [2]
  • computeIfAbsent() like Java [3]

I hope that we’ll take advantage of them so that we don’t hinder performance with onMissingKey. As a matter of fact I would prefer to have such methods added to Struct instead of onMissingKey.

I would also love to have an Unmodifiable Struct which we’ve discussed in the past.

As @Rory_Laitila mentioned it’d be also great to take advantage of Java Records but those require Java 14 so I don’t think that we’re ready for it.

[1] Map (Java Platform SE 8 )
[2] Map (Java Platform SE 8 )
[3] Map (Java Platform SE 8 )

1 Like

@bdw429s i do not want to go to much off topic on this. i can go into more details in a different place, if you like. Just the basics.

Context switching First of all, all this happens on runtime not on compile time, that makes it expensive.
Every time you enter a function Lucee changes how access to unscoped variables are handled and has to create (local, arguments) new scopes and make them available. When you leave the function it switches back to what it was before has to recycle (yes we recycle :wink: ) scopes if possible.

Because you can enter a function by multiple threads at the same time, that context/environment depends on the request and not the function alone. inside a function when you do dump(susi);, it will look for “susi” in the following places in that order.
local > arguments -> variables

when you are in a closure it is
local > arguments -> closureVariables
closureVariables = caller.local -> caller.arguments -> caller.variables

when you are in a component it is
local > arguments -> cfcVariables

That is what i call “context switching”.

Coming back to your proposal
when you can use this inside a literal struct, then every literal struct need to switch the context, a literal struct is

{susi:"Sorglos"}

internally we moved to “getOrDefault” in structs a log time ago. We could for example do a function like

structGetOrDefault(sct,key,default)

but i don’t like it to much, for 2 reasons:

  1. existing code does not benefit
  2. if “default” is a for example a function call it is always executed, even the key exist

so i would prefer if we simply improve the compiler on this

sct.key?:default

and yes “putIfAbsent()” and “computeIfAbsent()” could be good additions to the listener

I have made sure that struct that do not have listeners, have no performance downside because of that new feature.

Great discussion, i was thinking a lot about this. Like discussed this feature is between structs and components and so far we was talking about component light, because we adapt the idea from java, but in many ways it is different to java. You can say, in java the idea was to create a data structure/collection. In Lucee we already have this, we call it “structs”! But structs and beans are not the same, they have different downsides and upsides.

So maybe we should change the approach to this, it is not about making light component, it is about making “smart” structs. So we do not use the word “bean”, we simply do

// inline smart struct 
sct=struct {
    property name="lastName" type="string";
    property name="firstName" type="string";
    property name="middleName" type="string";
};

sct.lastName="Sorglos"; // works fine
sct.address="Föhnstrasse 1"; // will throw: property [address] is not allowed for this struct
sct.firstName=new MyNameComponent(); // will throw: invalid type, you cannot assign a value from type ...

but what about required properties

sct=struct {
    property name="lastName" type="string" required=true default="Sorglos";
    property name="firstName" type="string" required=true default="Susi";
    property name="middleName" type="string" required=false;
};
// or ?
sct=struct {
    property name="lastName" type="string" required=true;
    property name="firstName" type="string" required=true;
    property name="middleName" type="string" required=false;
}(lastName:"Sorglos",firstName:"susi"); // direcly populate as part of the construction

of course you still can do in a separate file

// Test.cfc (or other extension?)
struct  {
    property name="lastName" type="string" required=true;
    property name="firstName" type="string" required=true;
    property name="middleName" type="string" required=false;
}
// index.cfm
new Test(lastName:"Sorglos",firstName:"susi");
// or ?
createObject("struct","Test").init(lastName:"Sorglos",firstName:"susi");
// or ?
structNew("Test",[lastName:"Sorglos",firstName:"susi"]);

then you still can do

Test function whatever(Test test) {
   dump(test);
   return test;
}
whatever(new Test(lastName:"Sorglos",firstName:"susi"));

With all of that the functionality itself does not change at all, the only thing that changes is, how we do load it a little bit. We still get a structure with a clear interface and fix rules.

1 Like

How about something like this:

Employee type {  
  firstName: { type: string, required: true },
  lastName: { type: string, required: true },
  department: { type: string  },
  salary: { type: numeric, min: 10000, max: 200000, default: 10000, get: (s)=>lsCurrencyFormat(s, 'local', 'en_US') }
}

You could also define props using other types:

Employee type {  
  firstName: { type: string, required: true },
  lastName: { type: string, required: true },
  department: { type: Department },
  salary: { type: Salary }
}

Department type ["Sales", "IT", "Legal"];

Salary type {
  set: function(value) {
    if (!isEmpty(value) && !isNumeric(value)) {
       throw "Salary type is numeric";
    }
    return value;
  }
  get: function(value) {
    return (value) ? lsCurrencyFormat(value, 'local', 'en_US') : "Pending";
  }
}


mark = Employee {
  firstName: "Mark",
  lastName: "Green",
  department: "Sales",
  salary: 100000
}

mark.salary = 110000;

You could create types for arrays, structs and simple values.

Maybe this syntax would work:

type[]  type{}  type()

Arrays

Color type ["White", "Black", "Red", "Green", "Yellow"];

Palette type [
  type: Color,
  default: ["Black", "White"],
  maxlength: 3
]

pallete = Pallete.append("Red"); // ["Black", "White", "Red"]
pallete = Pallete["Blue", "White"]; // Fails validation
pallete = Pallete["Back", "White", "Red", "Green"]; // Fails validation

Simple Values

Salary type (
  type: numeric,
  default: 1000,
  min: 1000,
  max: 10000,
  get: (this) => lsCurrencyFormat(this, 'local', 'en_US')
)

salary = Salary();        // 1000
salary = Salary(200000);  // Fails validation

Structs

Name type {
  first: { type: string, required: true },
  last: { type: string, required: true },
  full: { set: false, get: (this) => this.first & " " & this.last }
}

Employee type {
  name: { type: Name, required: true },
  salary: { type: Salary, default: Salary() }
}

emp = Employee {
  name: {
    first: "Mark".
    last: "Green"
  }
}

emp.name.full     // "Mark Green" 
emp.salary        // $1,000
1 Like